From 8313d5e65165a61bc879762322256bd8e02861dd Mon Sep 17 00:00:00 2001 From: Klabukov Erik Date: Wed, 10 Jan 2024 22:51:21 +0500 Subject: [PATCH 1/4] Upgrade dotnet from 3.1 to 8 Upgraded nuget packages File scoped namespaces Half migrate to minimal api (move startup.cs to program.cs with new format) --- .vscode/launch.json | 68 +++---- .vscode/settings.json | 6 + Dockerfile | 58 +++--- src/Influunt.Feed.Rss/AtomRssEntity.cs | 168 +++++++++-------- src/Influunt.Feed.Rss/Extensions.cs | 114 ++++++------ .../Influunt.Feed.Rss.csproj | 48 ++--- src/Influunt.Feed.Rss/RssClient.cs | 51 +++-- src/Influunt.Feed.Rss/RssEntity.cs | 152 ++++++++------- .../RssFeedSourceProvider.cs | 85 +++++---- src/Influunt.Feed.Rss/RssModule.cs | 15 +- src/Influunt.Feed/Crawler/CrawlerOptions.cs | 11 +- .../Crawler/FeedCrawlerBackgroundWorker.cs | 135 +++++++------- src/Influunt.Feed/Entity/FavoriteFeedItem.cs | 9 +- src/Influunt.Feed/Entity/FeedChannel.cs | 31 ++-- src/Influunt.Feed/Entity/FeedItem.cs | 61 +++--- src/Influunt.Feed/Entity/User.cs | 67 ++++--- .../Extensions/HashExtensions.cs | 27 ++- .../Extensions/IQueryableExtensions.cs | 23 ++- src/Influunt.Feed/IChannelService.cs | 73 ++++---- src/Influunt.Feed/IFavoriteFeedService.cs | 45 +++-- src/Influunt.Feed/IFeedService.cs | 71 ++++--- src/Influunt.Feed/IFeedSourceProvider.cs | 31 ++-- src/Influunt.Feed/IUserService.cs | 115 ++++++------ src/Influunt.Feed/Influunt.Feed.csproj | 36 ++-- src/Influunt.Host/Influunt.Host.csproj | 10 +- src/Influunt.Host/Program.cs | 91 ++++++--- src/Influunt.Host/Startup.cs | 70 ------- src/Influunt.Host/StartupModule.cs | 83 ++++----- src/Influunt.Host/WebModule.cs | 93 +++++----- src/Influunt.Storage/Influunt.Storage.csproj | 2 +- src/Influunt.Storage/StorageConfiguration.cs | 11 +- src/Influunt.Storage/StorageModule.cs | 175 +++++++++--------- 32 files changed, 987 insertions(+), 1048 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 src/Influunt.Host/Startup.cs diff --git a/.vscode/launch.json b/.vscode/launch.json index c818806..859b02f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,35 +1,35 @@ -{ - "version": "0.2.0", - "configurations": [ - { - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md - "name": ".NET Core Launch (web)", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/src/Influunt.Host/bin/Debug/netcoreapp3.1/Influunt.Host.dll", - "args": [], - "cwd": "${workspaceFolder}/src/Influunt.Host", - "stopAtEntry": false, - // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser - "serverReadyAction": { - "action": "openExternally", - "pattern": "\\bNow listening on:\\s+(https?://\\S+)" - }, - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "sourceFileMap": { - "/Views": "${workspaceFolder}/Views" - } - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach" - } - ] +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/src/Influunt.Host/bin/Debug/net8/Influunt.Host.dll", + "args": [], + "cwd": "${workspaceFolder}/src/Influunt.Host", + "stopAtEntry": false, + // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..acf2f15 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "cSpell.words": [ + "Influunt", + "Skidbladnir" + ] +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index ebf9516..34c6e6b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,29 +1,29 @@ -FROM alpine/git as version -WORKDIR /src -COPY . /src -RUN echo $(git describe --tags --always 2>/dev/null | sed 's/-g[a-z0-9]\{7\}//') > /version ;\ - echo "Version: "$(cat /version) - -FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build -COPY . /build -COPY --from=version /version /build/version -WORKDIR /build -RUN apt-get update -yq ;\ - apt-get install curl gnupg -yq ;\ - curl -sL https://deb.nodesource.com/setup_14.x | bash - ;\ - apt-get install -y nodejs - -RUN sed -i -e "s/0-develop<\/Version>/$(cat version | cut -c2- )<\/Version>/g" src/Influunt.Host/Influunt.Host.csproj;\ - dotnet restore -s https://api.nuget.org/v3/index.json; \ - dotnet build --no-restore -c Release; \ - dotnet publish ./src/Influunt.Host/Influunt.Host.csproj -c Release -o /app --no-build; \ - dotnet nuget locals http-cache --clear;\ - dotnet nuget locals temp --clear - - -######## Influunt Host -FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim as influunt -COPY --from=build /app /influunt -WORKDIR /influunt -EXPOSE 80 -ENTRYPOINT ["dotnet", "Influunt.Host.dll"] +FROM alpine/git as version +WORKDIR /src +COPY . /src +RUN echo $(git describe --tags --always 2>/dev/null | sed 's/-g[a-z0-9]\{7\}//') > /version ;\ + echo "Version: "$(cat /version) + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +COPY . /build +COPY --from=version /version /build/version +WORKDIR /build +RUN apt-get update -yq ;\ + apt-get install curl gnupg -yq ;\ + curl -sL https://deb.nodesource.com/setup_14.x | bash - ;\ + apt-get install -y nodejs + +RUN sed -i -e "s/0-develop<\/Version>/$(cat version | cut -c2- )<\/Version>/g" src/Influunt.Host/Influunt.Host.csproj;\ + dotnet restore -s https://api.nuget.org/v3/index.json; \ + dotnet build --no-restore -c Release; \ + dotnet publish ./src/Influunt.Host/Influunt.Host.csproj -c Release -o /app --no-build; \ + dotnet nuget locals http-cache --clear;\ + dotnet nuget locals temp --clear + + +######## Influunt Host +FROM mcr.microsoft.com/dotnet/aspnet:8.0-bookworm-slim as influunt +COPY --from=build /app /influunt +WORKDIR /influunt +EXPOSE 80 +ENTRYPOINT ["dotnet", "Influunt.Host.dll"] diff --git a/src/Influunt.Feed.Rss/AtomRssEntity.cs b/src/Influunt.Feed.Rss/AtomRssEntity.cs index 13e7418..e217e30 100644 --- a/src/Influunt.Feed.Rss/AtomRssEntity.cs +++ b/src/Influunt.Feed.Rss/AtomRssEntity.cs @@ -1,86 +1,84 @@ -using System.Collections.Generic; -using System.Xml.Serialization; - -namespace Influunt.Feed.Rss -{ - [XmlRoot(ElementName = "generator", Namespace = "http://www.w3.org/2005/Atom")] - public class Generator - { - [XmlAttribute(AttributeName = "uri")] - public string Uri { get; set; } - [XmlAttribute(AttributeName = "version")] - public string Version { get; set; } - [XmlText] - public string Text { get; set; } - } - - [XmlRoot(ElementName = "link", Namespace = "http://www.w3.org/2005/Atom")] - public class Link - { - [XmlAttribute(AttributeName = "href")] - public string Href { get; set; } - [XmlAttribute(AttributeName = "rel")] - public string Rel { get; set; } - [XmlAttribute(AttributeName = "type")] - public string Type { get; set; } - [XmlAttribute(AttributeName = "title")] - public string Title { get; set; } - } - - [XmlRoot(ElementName = "content", Namespace = "http://www.w3.org/2005/Atom")] - public class Content - { - [XmlAttribute(AttributeName = "type")] - public string Type { get; set; } - [XmlAttribute(AttributeName = "base", Namespace = "http://www.w3.org/XML/1998/namespace")] - public string Base { get; set; } - [XmlText] - public string Text { get; set; } - } - - [XmlRoot(ElementName = "category", Namespace = "http://www.w3.org/2005/Atom")] - public class Category - { - [XmlAttribute(AttributeName = "term")] - public string Term { get; set; } - } - - [XmlRoot(ElementName = "entry", Namespace = "http://www.w3.org/2005/Atom")] - public class Entry - { - [XmlElement(ElementName = "title", Namespace = "http://www.w3.org/2005/Atom")] - public string Title { get; set; } - [XmlElement(ElementName = "link", Namespace = "http://www.w3.org/2005/Atom")] - public Link Link { get; set; } - [XmlElement(ElementName = "published", Namespace = "http://www.w3.org/2005/Atom")] - public string Published { get; set; } - [XmlElement(ElementName = "updated", Namespace = "http://www.w3.org/2005/Atom")] - public string Updated { get; set; } - [XmlElement(ElementName = "id", Namespace = "http://www.w3.org/2005/Atom")] - public string Id { get; set; } - [XmlElement(ElementName = "content", Namespace = "http://www.w3.org/2005/Atom")] - public Content Content { get; set; } - [XmlElement(ElementName = "category", Namespace = "http://www.w3.org/2005/Atom")] - public Category Category { get; set; } - [XmlElement(ElementName = "summary", Namespace = "http://www.w3.org/2005/Atom")] - public string Summary { get; set; } - } - - [XmlRoot(ElementName = "feed", Namespace = "http://www.w3.org/2005/Atom")] - public class Feed - { - [XmlElement(ElementName = "generator", Namespace = "http://www.w3.org/2005/Atom")] - public Generator Generator { get; set; } - [XmlElement(ElementName = "link", Namespace = "http://www.w3.org/2005/Atom")] - public List Link { get; set; } - [XmlElement(ElementName = "updated", Namespace = "http://www.w3.org/2005/Atom")] - public string Updated { get; set; } - [XmlElement(ElementName = "id", Namespace = "http://www.w3.org/2005/Atom")] - public string Id { get; set; } - [XmlElement(ElementName = "entry", Namespace = "http://www.w3.org/2005/Atom")] - public List Entry { get; set; } - [XmlAttribute(AttributeName = "xmlns")] - public string Xmlns { get; set; } - } - +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Influunt.Feed.Rss; + +[XmlRoot(ElementName = "generator", Namespace = "http://www.w3.org/2005/Atom")] +public class Generator +{ + [XmlAttribute(AttributeName = "uri")] + public string Uri { get; set; } + [XmlAttribute(AttributeName = "version")] + public string Version { get; set; } + [XmlText] + public string Text { get; set; } +} + +[XmlRoot(ElementName = "link", Namespace = "http://www.w3.org/2005/Atom")] +public class Link +{ + [XmlAttribute(AttributeName = "href")] + public string Href { get; set; } + [XmlAttribute(AttributeName = "rel")] + public string Rel { get; set; } + [XmlAttribute(AttributeName = "type")] + public string Type { get; set; } + [XmlAttribute(AttributeName = "title")] + public string Title { get; set; } +} + +[XmlRoot(ElementName = "content", Namespace = "http://www.w3.org/2005/Atom")] +public class Content +{ + [XmlAttribute(AttributeName = "type")] + public string Type { get; set; } + [XmlAttribute(AttributeName = "base", Namespace = "http://www.w3.org/XML/1998/namespace")] + public string Base { get; set; } + [XmlText] + public string Text { get; set; } +} + +[XmlRoot(ElementName = "category", Namespace = "http://www.w3.org/2005/Atom")] +public class Category +{ + [XmlAttribute(AttributeName = "term")] + public string Term { get; set; } +} + +[XmlRoot(ElementName = "entry", Namespace = "http://www.w3.org/2005/Atom")] +public class Entry +{ + [XmlElement(ElementName = "title", Namespace = "http://www.w3.org/2005/Atom")] + public string Title { get; set; } + [XmlElement(ElementName = "link", Namespace = "http://www.w3.org/2005/Atom")] + public Link Link { get; set; } + [XmlElement(ElementName = "published", Namespace = "http://www.w3.org/2005/Atom")] + public string Published { get; set; } + [XmlElement(ElementName = "updated", Namespace = "http://www.w3.org/2005/Atom")] + public string Updated { get; set; } + [XmlElement(ElementName = "id", Namespace = "http://www.w3.org/2005/Atom")] + public string Id { get; set; } + [XmlElement(ElementName = "content", Namespace = "http://www.w3.org/2005/Atom")] + public Content Content { get; set; } + [XmlElement(ElementName = "category", Namespace = "http://www.w3.org/2005/Atom")] + public Category Category { get; set; } + [XmlElement(ElementName = "summary", Namespace = "http://www.w3.org/2005/Atom")] + public string Summary { get; set; } +} + +[XmlRoot(ElementName = "feed", Namespace = "http://www.w3.org/2005/Atom")] +public class Feed +{ + [XmlElement(ElementName = "generator", Namespace = "http://www.w3.org/2005/Atom")] + public Generator Generator { get; set; } + [XmlElement(ElementName = "link", Namespace = "http://www.w3.org/2005/Atom")] + public List Link { get; set; } + [XmlElement(ElementName = "updated", Namespace = "http://www.w3.org/2005/Atom")] + public string Updated { get; set; } + [XmlElement(ElementName = "id", Namespace = "http://www.w3.org/2005/Atom")] + public string Id { get; set; } + [XmlElement(ElementName = "entry", Namespace = "http://www.w3.org/2005/Atom")] + public List Entry { get; set; } + [XmlAttribute(AttributeName = "xmlns")] + public string Xmlns { get; set; } } \ No newline at end of file diff --git a/src/Influunt.Feed.Rss/Extensions.cs b/src/Influunt.Feed.Rss/Extensions.cs index 2c229e2..485436d 100644 --- a/src/Influunt.Feed.Rss/Extensions.cs +++ b/src/Influunt.Feed.Rss/Extensions.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using System.Xml.Serialization; @@ -10,81 +9,80 @@ using Microsoft.Extensions.Caching.Distributed; using Newtonsoft.Json; -namespace Influunt.Feed.Rss +namespace Influunt.Feed.Rss; + +public static class Extensions { - public static class Extensions + public static async Task GetAsync(this IDistributedCache cache, string key) { - public static async Task GetAsync(this IDistributedCache cache, string key) - { - var cacheValue = await cache.GetStringAsync(key); - if (string.IsNullOrWhiteSpace(cacheValue)) - return default; - return JsonConvert.DeserializeObject(cacheValue); - } + var cacheValue = await cache.GetStringAsync(key); + if (string.IsNullOrWhiteSpace(cacheValue)) + return default; + return JsonConvert.DeserializeObject(cacheValue); + } - public static Task SetAsync(this IDistributedCache cache, string key, object entry, - DistributedCacheEntryOptions options) - { - var serializedEntry = JsonConvert.SerializeObject(entry); - return cache.SetAsync(key, Encoding.UTF8.GetBytes(serializedEntry), options); - } + public static Task SetAsync(this IDistributedCache cache, string key, object entry, + DistributedCacheEntryOptions options) + { + var serializedEntry = JsonConvert.SerializeObject(entry); + return cache.SetAsync(key, Encoding.UTF8.GetBytes(serializedEntry), options); + } - public static List AsList(this IEnumerable source) - => (source == null || source is List) ? (List)source : source.ToList(); + public static List AsList(this IEnumerable source) + => (source == null || source is List) ? (List)source : source.ToList(); - public static bool IsAtomRss(this string xml) - { - return xml.Contains("xmlns=\"http://www.w3.org/2005/Atom\""); - } + public static bool IsAtomRss(this string xml) + { + return xml.Contains("xmlns=\"http://www.w3.org/2005/Atom\""); + } - public static List FeedFromRss(this string xml) + public static List FeedFromRss(this string xml) + { + using (var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(xml ?? ""))) { - using (var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(xml ?? ""))) + var ser = new XmlSerializer(typeof(RssBody)); + var rssBody = (RssBody)ser.Deserialize(memoryStream); + return rssBody.Channel.Item.Select(rssItem => { - var ser = new XmlSerializer(typeof(RssBody)); - var rssBody = (RssBody)ser.Deserialize(memoryStream); - return rssBody.Channel.Item.Select(rssItem => + var item = new FeedItem { - var item = new FeedItem - { - Title = rssItem.Title, - Description = rssItem.Description, - PubDate = DateTime.UtcNow, - Link = rssItem.Link?.ToString(), - }; + Title = rssItem.Title, + Description = rssItem.Description, + PubDate = DateTime.UtcNow, + Link = rssItem.Link?.ToString(), + }; - if (DateTime.TryParse(rssItem.PubDate, out var pubDate)) - item.PubDate = pubDate; + if (DateTime.TryParse(rssItem.PubDate, out var pubDate)) + item.PubDate = pubDate; - return item.NormalizeDescription(); - }).ToList(); - } + return item.NormalizeDescription(); + }).ToList(); } + } - public static List FeedFromAtomRss(this string xml) + public static List FeedFromAtomRss(this string xml) + { + using (var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(xml ?? ""))) { - using (var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(xml ?? ""))) + var ser = new XmlSerializer(typeof(Feed)); + var rssBody = (Feed)ser.Deserialize(memoryStream); + return rssBody.Entry.Select(rssItem => { - var ser = new XmlSerializer(typeof(Feed)); - var rssBody = (Feed)ser.Deserialize(memoryStream); - return rssBody.Entry.Select(rssItem => + var item = new FeedItem { - var item = new FeedItem - { - Title = rssItem.Title, - Description = string.IsNullOrEmpty(rssItem.Content?.Text) - ? rssItem.Summary - : rssItem.Content.Text, - PubDate = DateTime.UtcNow, - Link = rssItem.Link?.ToString(), - }; + Title = rssItem.Title, + Description = string.IsNullOrEmpty(rssItem.Content?.Text) + ? rssItem.Summary + : rssItem.Content.Text, + PubDate = DateTime.UtcNow, + Link = rssItem.Link?.ToString(), + }; - if (DateTime.TryParse(rssItem.Published, out var pubDate)) - item.PubDate = pubDate; + if (DateTime.TryParse(rssItem.Published, out var pubDate)) + item.PubDate = pubDate; - return item.NormalizeDescription(); - }).ToList(); - } + return item.NormalizeDescription(); + }).ToList(); } } } \ No newline at end of file diff --git a/src/Influunt.Feed.Rss/Influunt.Feed.Rss.csproj b/src/Influunt.Feed.Rss/Influunt.Feed.Rss.csproj index 182aa4e..81736b3 100644 --- a/src/Influunt.Feed.Rss/Influunt.Feed.Rss.csproj +++ b/src/Influunt.Feed.Rss/Influunt.Feed.Rss.csproj @@ -1,25 +1,25 @@ - - - - netstandard2.0 - Klabukov Erik - Influunt - Influunt - Simple Rss Agregator - Klabukov Erik - - - - - - - - - - - - - - - - + + + + net8 + Klabukov Erik + Influunt + Influunt - Simple Rss Agregator + Klabukov Erik + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Influunt.Feed.Rss/RssClient.cs b/src/Influunt.Feed.Rss/RssClient.cs index 65b6989..67e6774 100644 --- a/src/Influunt.Feed.Rss/RssClient.cs +++ b/src/Influunt.Feed.Rss/RssClient.cs @@ -5,38 +5,37 @@ using Influunt.Feed.Entity; using Microsoft.Extensions.Logging; -namespace Influunt.Feed.Rss +namespace Influunt.Feed.Rss; + +internal class RssClient { - internal class RssClient - { - private readonly HttpClient _httpClient; - private readonly ILogger _logger; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; - public RssClient(HttpClient httpClient, ILogger logger) - { - _httpClient = httpClient; - _logger = logger; - } + public RssClient(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + } - public async Task> GetFeed(FeedChannel channel) + public async Task> GetFeed(FeedChannel channel) + { + try { - try - { - using (var result = await _httpClient.GetAsync(channel.Url)) - { - var xmlRss = await result.Content.ReadAsStringAsync(); - return xmlRss.IsAtomRss() - ? xmlRss.FeedFromAtomRss() - : xmlRss.FeedFromRss(); - } - } - catch (Exception e) + using (var result = await _httpClient.GetAsync(channel.Url)) { - _logger.LogError( - "Can not get rss feed from \nchannel: {channelName}\nurl: {channelUrl}\n with error: {message} ", - channel.Name, channel.Url, e.Message); - return Array.Empty(); + var xmlRss = await result.Content.ReadAsStringAsync(); + return xmlRss.IsAtomRss() + ? xmlRss.FeedFromAtomRss() + : xmlRss.FeedFromRss(); } } + catch (Exception e) + { + _logger.LogError( + "Can not get rss feed from \nchannel: {channelName}\nurl: {channelUrl}\n with error: {message} ", + channel.Name, channel.Url, e.Message); + return Array.Empty(); + } } } \ No newline at end of file diff --git a/src/Influunt.Feed.Rss/RssEntity.cs b/src/Influunt.Feed.Rss/RssEntity.cs index 0e00015..78d7c99 100644 --- a/src/Influunt.Feed.Rss/RssEntity.cs +++ b/src/Influunt.Feed.Rss/RssEntity.cs @@ -1,78 +1,76 @@ -using System.Collections.Generic; -using System.Xml.Serialization; -namespace Influunt.Feed.Rss -{ - [XmlRoot(ElementName = "image")] - public class Image - { - [XmlElement(ElementName = "link")] - public string Link { get; set; } - [XmlElement(ElementName = "url")] - public string Url { get; set; } - [XmlElement(ElementName = "title")] - public string Title { get; set; } - } - - [XmlRoot(ElementName = "guid")] - public class Guid - { - [XmlAttribute(AttributeName = "isPermaLink")] - public string IsPermaLink { get; set; } - [XmlText] - public string Text { get; set; } - } - - [XmlRoot(ElementName = "item")] - public class Item - { - [XmlElement(ElementName = "title")] - public string Title { get; set; } - [XmlElement(ElementName = "guid")] - public Guid Guid { get; set; } - [XmlElement(ElementName = "link")] - public string Link { get; set; } - [XmlElement(ElementName = "description")] - public string Description { get; set; } - [XmlElement(ElementName = "pubDate")] - public string PubDate { get; set; } - [XmlElement(ElementName = "creator", Namespace = "http://purl.org/dc/elements/1.1/")] - public string Creator { get; set; } - [XmlElement(ElementName = "category")] - public List Category { get; set; } - } - - [XmlRoot(ElementName = "channel")] - public class Channel - { - [XmlElement(ElementName = "title")] - public string Title { get; set; } - [XmlElement(ElementName = "link")] - public string Link { get; set; } - [XmlElement(ElementName = "description")] - public string Description { get; set; } - [XmlElement(ElementName = "language")] - public string Language { get; set; } - [XmlElement(ElementName = "managingEditor")] - public string ManagingEditor { get; set; } - [XmlElement(ElementName = "generator")] - public string Generator { get; set; } - [XmlElement(ElementName = "pubDate")] - public string PubDate { get; set; } - [XmlElement(ElementName = "image")] - public Image Image { get; set; } - [XmlElement(ElementName = "item")] - public List Item { get; set; } - } - - [XmlRoot(ElementName = "rss")] - public class RssBody - { - [XmlElement(ElementName = "channel")] - public Channel Channel { get; set; } - [XmlAttribute(AttributeName = "version")] - public string Version { get; set; } - [XmlAttribute(AttributeName = "dc", Namespace = "http://www.w3.org/2000/xmlns/")] - public string Dc { get; set; } - } - +using System.Collections.Generic; +using System.Xml.Serialization; +namespace Influunt.Feed.Rss; + +[XmlRoot(ElementName = "image")] +public class Image +{ + [XmlElement(ElementName = "link")] + public string Link { get; set; } + [XmlElement(ElementName = "url")] + public string Url { get; set; } + [XmlElement(ElementName = "title")] + public string Title { get; set; } +} + +[XmlRoot(ElementName = "guid")] +public class Guid +{ + [XmlAttribute(AttributeName = "isPermaLink")] + public string IsPermaLink { get; set; } + [XmlText] + public string Text { get; set; } +} + +[XmlRoot(ElementName = "item")] +public class Item +{ + [XmlElement(ElementName = "title")] + public string Title { get; set; } + [XmlElement(ElementName = "guid")] + public Guid Guid { get; set; } + [XmlElement(ElementName = "link")] + public string Link { get; set; } + [XmlElement(ElementName = "description")] + public string Description { get; set; } + [XmlElement(ElementName = "pubDate")] + public string PubDate { get; set; } + [XmlElement(ElementName = "creator", Namespace = "http://purl.org/dc/elements/1.1/")] + public string Creator { get; set; } + [XmlElement(ElementName = "category")] + public List Category { get; set; } +} + +[XmlRoot(ElementName = "channel")] +public class Channel +{ + [XmlElement(ElementName = "title")] + public string Title { get; set; } + [XmlElement(ElementName = "link")] + public string Link { get; set; } + [XmlElement(ElementName = "description")] + public string Description { get; set; } + [XmlElement(ElementName = "language")] + public string Language { get; set; } + [XmlElement(ElementName = "managingEditor")] + public string ManagingEditor { get; set; } + [XmlElement(ElementName = "generator")] + public string Generator { get; set; } + [XmlElement(ElementName = "pubDate")] + public string PubDate { get; set; } + [XmlElement(ElementName = "image")] + public Image Image { get; set; } + [XmlElement(ElementName = "item")] + public List Item { get; set; } +} + +[XmlRoot(ElementName = "rss")] +public class RssBody +{ + [XmlElement(ElementName = "channel")] + public Channel Channel { get; set; } + [XmlAttribute(AttributeName = "version")] + public string Version { get; set; } + [XmlAttribute(AttributeName = "dc", Namespace = "http://www.w3.org/2000/xmlns/")] + public string Dc { get; set; } } \ No newline at end of file diff --git a/src/Influunt.Feed.Rss/RssFeedSourceProvider.cs b/src/Influunt.Feed.Rss/RssFeedSourceProvider.cs index 2429999..59b1676 100644 --- a/src/Influunt.Feed.Rss/RssFeedSourceProvider.cs +++ b/src/Influunt.Feed.Rss/RssFeedSourceProvider.cs @@ -7,51 +7,50 @@ using System.Threading.Tasks; using Influunt.Feed.Entity; -namespace Influunt.Feed.Rss +namespace Influunt.Feed.Rss; + +internal class RssFeedSourceProvider : IFeedSourceProvider, IDisposable { - internal class RssFeedSourceProvider : IFeedSourceProvider, IDisposable + private readonly IDistributedCache _distributedCache; + private readonly ILogger _logger; + private readonly RssClient _rssClient; + + public RssFeedSourceProvider(RssClient rssClient, IDistributedCache distributedCache, + ILogger logger) { - private readonly IDistributedCache _distributedCache; - private readonly ILogger _logger; - private readonly RssClient _rssClient; - - public RssFeedSourceProvider(RssClient rssClient, IDistributedCache distributedCache, - ILogger logger) - { - _rssClient = rssClient; - _distributedCache = distributedCache; - _logger = logger; - } - - public async Task> GetRemoteFeed(FeedChannel channel) - { - var sw = Stopwatch.StartNew(); - var remoteFeed = await GetFeedFromChannelCached(channel); - _logger.LogDebug($"Elapsed time for getting channel ({channel.Id}) feed: {sw.Elapsed.TotalMilliseconds}ms"); - return remoteFeed; - } - - public bool CanProcessChannel(FeedChannel channel) => true; - - public void Dispose() - { - GC.Collect(); - GC.SuppressFinalize(this); - } - - private async Task> GetFeedFromChannelCached(FeedChannel channel) - { - var channelFeed = await _distributedCache.GetAsync>($"channel_url_{channel.Url}"); - if (channelFeed != null && channelFeed.Count != 0) - return channelFeed; - - channelFeed = (await _rssClient.GetFeed(channel)).AsList(); - if (channelFeed.Any()) - await _distributedCache.SetAsync($"channel_url_{channel.Url}", channelFeed, new DistributedCacheEntryOptions() - { - AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) - }); + _rssClient = rssClient; + _distributedCache = distributedCache; + _logger = logger; + } + + public async Task> GetRemoteFeed(FeedChannel channel) + { + var sw = Stopwatch.StartNew(); + var remoteFeed = await GetFeedFromChannelCached(channel); + _logger.LogDebug($"Elapsed time for getting channel ({channel.Id}) feed: {sw.Elapsed.TotalMilliseconds}ms"); + return remoteFeed; + } + + public bool CanProcessChannel(FeedChannel channel) => true; + + public void Dispose() + { + GC.Collect(); + GC.SuppressFinalize(this); + } + + private async Task> GetFeedFromChannelCached(FeedChannel channel) + { + var channelFeed = await _distributedCache.GetAsync>($"channel_url_{channel.Url}"); + if (channelFeed != null && channelFeed.Count != 0) return channelFeed; - } + + channelFeed = (await _rssClient.GetFeed(channel)).AsList(); + if (channelFeed.Any()) + await _distributedCache.SetAsync($"channel_url_{channel.Url}", channelFeed, new DistributedCacheEntryOptions() + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) + }); + return channelFeed; } } \ No newline at end of file diff --git a/src/Influunt.Feed.Rss/RssModule.cs b/src/Influunt.Feed.Rss/RssModule.cs index f79306a..e4016d5 100644 --- a/src/Influunt.Feed.Rss/RssModule.cs +++ b/src/Influunt.Feed.Rss/RssModule.cs @@ -2,15 +2,14 @@ using System.Text; using Skidbladnir.Modules; -namespace Influunt.Feed.Rss +namespace Influunt.Feed.Rss; + +public class RssModule : Module { - public class RssModule : Module + public override void Configure(IServiceCollection services) { - public override void Configure(IServiceCollection services) - { - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - services.AddHttpClient(); - services.AddSingleton(); - } + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + services.AddHttpClient(); + services.AddSingleton(); } } \ No newline at end of file diff --git a/src/Influunt.Feed/Crawler/CrawlerOptions.cs b/src/Influunt.Feed/Crawler/CrawlerOptions.cs index 8bf4a37..3ca4f80 100644 --- a/src/Influunt.Feed/Crawler/CrawlerOptions.cs +++ b/src/Influunt.Feed/Crawler/CrawlerOptions.cs @@ -1,10 +1,9 @@ using System; -namespace Influunt.Feed.Crawler +namespace Influunt.Feed.Crawler; + +public class CrawlerOptions { - public class CrawlerOptions - { - public TimeSpan FetchInterval { get; set; } = TimeSpan.FromMinutes(30); - public int LastActivityDaysAgo { get; set; } = 31*3; // 3 Month default - } + public TimeSpan FetchInterval { get; set; } = TimeSpan.FromMinutes(30); + public int LastActivityDaysAgo { get; set; } = 31*3; // 3 Month default } \ No newline at end of file diff --git a/src/Influunt.Feed/Crawler/FeedCrawlerBackgroundWorker.cs b/src/Influunt.Feed/Crawler/FeedCrawlerBackgroundWorker.cs index c9b09e1..6907852 100644 --- a/src/Influunt.Feed/Crawler/FeedCrawlerBackgroundWorker.cs +++ b/src/Influunt.Feed/Crawler/FeedCrawlerBackgroundWorker.cs @@ -9,88 +9,87 @@ using Microsoft.Extensions.Options; using Skidbladnir.Utility.Common; -namespace Influunt.Feed.Crawler +namespace Influunt.Feed.Crawler; + +public class FeedCrawlerBackgroundWorker : BackgroundService { - public class FeedCrawlerBackgroundWorker : BackgroundService - { - private readonly IEnumerable _feedSourceProviders; - private readonly IFeedService _feedService; - private readonly IUserService _userService; - private readonly IChannelService _channelService; - private readonly CrawlerOptions _options; - private readonly ILogger _logger; + private readonly IEnumerable _feedSourceProviders; + private readonly IFeedService _feedService; + private readonly IUserService _userService; + private readonly IChannelService _channelService; + private readonly CrawlerOptions _options; + private readonly ILogger _logger; - public FeedCrawlerBackgroundWorker(IEnumerable feedSourceProviders, - IFeedService feedService, IUserService userService, - IOptions options, - IChannelService channelService, - ILogger logger) - { - _feedSourceProviders = feedSourceProviders; - _feedService = feedService; - _userService = userService; - _channelService = channelService; - _options = options.Value; - _logger = logger; - } + public FeedCrawlerBackgroundWorker(IEnumerable feedSourceProviders, + IFeedService feedService, IUserService userService, + IOptions options, + IChannelService channelService, + ILogger logger) + { + _feedSourceProviders = feedSourceProviders; + _feedService = feedService; + _userService = userService; + _channelService = channelService; + _options = options.Value; + _logger = logger; + } - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - _logger.LogInformation("Feed Crawler Background Worker running."); + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + _logger.LogInformation("Feed Crawler Background Worker running."); - while (!stoppingToken.IsCancellationRequested) + while (!stoppingToken.IsCancellationRequested) + { + try { - try - { - await InnerExecute(stoppingToken); - } - catch (Exception e) - { - if (e is OperationCanceledException) - break; - _logger.LogError(e, "Error in Feed Crawler Background Worker when fetch feeds."); - } - await Task.Delay(_options.FetchInterval); + await InnerExecute(stoppingToken); } - _logger.LogInformation("Feed Crawler Background Worker is stopping."); - + catch (Exception e) + { + if (e is OperationCanceledException) + break; + _logger.LogError(e, "Error in Feed Crawler Background Worker when fetch feeds."); + } + await Task.Delay(_options.FetchInterval); } + _logger.LogInformation("Feed Crawler Background Worker is stopping."); + + } - private async Task InnerExecute(CancellationToken token) + private async Task InnerExecute(CancellationToken token) + { + var minimumLastActivityDate = DateTime.UtcNow - TimeSpan.FromDays(_options.LastActivityDaysAgo); + var users = await _userService.GetUsers(); + token.ThrowIfCancellationRequested(); + foreach (var user in users.Where(x => x.LastActivity > minimumLastActivityDate)) { - var minimumLastActivityDate = DateTime.UtcNow - TimeSpan.FromDays(_options.LastActivityDaysAgo); - var users = await _userService.GetUsers(); token.ThrowIfCancellationRequested(); - foreach (var user in users.Where(x => x.LastActivity > minimumLastActivityDate)) + var channels = await Try.DoAsync(() => _channelService.GetUserChannels(user)); + if (channels == null) + continue; + try + { + var fetchTasks = channels.Select(x => FetchFeedFromChannel(x, user, token)); + await Task.WhenAll(fetchTasks); + } + catch (Exception e) { - token.ThrowIfCancellationRequested(); - var channels = await Try.DoAsync(() => _channelService.GetUserChannels(user)); - if (channels == null) - continue; - try - { - var fetchTasks = channels.Select(x => FetchFeedFromChannel(x, user, token)); - await Task.WhenAll(fetchTasks); - } - catch (Exception e) - { - if (e is OperationCanceledException) - throw; - _logger.LogError(e, $"Can't fetch feed for user {user.Id}"); - } + if (e is OperationCanceledException) + throw; + _logger.LogError(e, $"Can't fetch feed for user {user.Id}"); } } + } - private async Task FetchFeedFromChannel(FeedChannel channel, User user, CancellationToken token) - { - token.ThrowIfCancellationRequested(); - var remoteFeedProvider = _feedSourceProviders.FirstOrDefault(x => x.CanProcessChannel(channel)); - if (remoteFeedProvider == null) - return; - var remoteFeed = await remoteFeedProvider.GetRemoteFeed(channel); - var count = await _feedService.TryAddToFeed(user, remoteFeed, channel); - _logger.LogInformation($"For user {user.Id} added {count} posts from channel {channel.Id}"); - } + private async Task FetchFeedFromChannel(FeedChannel channel, User user, CancellationToken token) + { + token.ThrowIfCancellationRequested(); + var remoteFeedProvider = _feedSourceProviders.FirstOrDefault(x => x.CanProcessChannel(channel)); + if (remoteFeedProvider == null) + return; + var remoteFeed = await remoteFeedProvider.GetRemoteFeed(channel); + var count = await _feedService.TryAddToFeed(user, remoteFeed, channel); + _logger.LogInformation($"For user {user.Id} added {count} posts from channel {channel.Id}"); } } \ No newline at end of file diff --git a/src/Influunt.Feed/Entity/FavoriteFeedItem.cs b/src/Influunt.Feed/Entity/FavoriteFeedItem.cs index 20b9464..bfb24b9 100644 --- a/src/Influunt.Feed/Entity/FavoriteFeedItem.cs +++ b/src/Influunt.Feed/Entity/FavoriteFeedItem.cs @@ -1,9 +1,6 @@ -using System; +namespace Influunt.Feed.Entity; -namespace Influunt.Feed.Entity +public class FavoriteFeedItem : FeedItem { - public class FavoriteFeedItem : FeedItem - { - public string ChannelName { get; set; } - } + public string ChannelName { get; set; } } \ No newline at end of file diff --git a/src/Influunt.Feed/Entity/FeedChannel.cs b/src/Influunt.Feed/Entity/FeedChannel.cs index cc1c346..24d23d4 100644 --- a/src/Influunt.Feed/Entity/FeedChannel.cs +++ b/src/Influunt.Feed/Entity/FeedChannel.cs @@ -1,17 +1,16 @@ -using Skidbladnir.Repository.Abstractions; - -namespace Influunt.Feed.Entity -{ - public class FeedChannel : IHasId - { - public string Url { get; set; } - - public string Name { get; set; } - - public string UserId { get; set; } - - public bool Hidden { get; set; } = false; - - public string Id { get; set; } - } +using Skidbladnir.Repository.Abstractions; + +namespace Influunt.Feed.Entity; + +public class FeedChannel : IHasId +{ + public string Url { get; set; } + + public string Name { get; set; } + + public string UserId { get; set; } + + public bool Hidden { get; set; } = false; + + public string Id { get; set; } } \ No newline at end of file diff --git a/src/Influunt.Feed/Entity/FeedItem.cs b/src/Influunt.Feed/Entity/FeedItem.cs index 6510d50..08e700b 100644 --- a/src/Influunt.Feed/Entity/FeedItem.cs +++ b/src/Influunt.Feed/Entity/FeedItem.cs @@ -1,32 +1,31 @@ -using System; -using System.Text.RegularExpressions; -using System.Web; -using Skidbladnir.Repository.Abstractions; - -namespace Influunt.Feed.Entity -{ - public class FeedItem : IHasId - { - public string Id { get; set; } - public string Title { get; set; } - public string Link { get; set; } - public string Description { get; set; } - public DateTime PubDate { get; set; } - public string UserId { get; set; } - public string ChannelId { get; set; } - public string Hash { get; set; } - - public FeedItem NormalizeDescription() - { - if (Description == null) return this; - - Description = HttpUtility.HtmlDecode(Description); - Description = Regex.Replace(Description, @"\", "", RegexOptions.IgnoreCase); - Description = Regex.Replace(Description, @"\", "", RegexOptions.IgnoreCase); - Description = Regex.Replace(Description, @"\", "", RegexOptions.IgnoreCase); - Description = Regex.Replace(Description, @"\", "", RegexOptions.IgnoreCase); - Description = Regex.Replace(Description, @"\", "", RegexOptions.IgnoreCase); - return this; - } - } +using System; +using System.Text.RegularExpressions; +using System.Web; +using Skidbladnir.Repository.Abstractions; + +namespace Influunt.Feed.Entity; + +public class FeedItem : IHasId +{ + public string Id { get; set; } + public string Title { get; set; } + public string Link { get; set; } + public string Description { get; set; } + public DateTime PubDate { get; set; } + public string UserId { get; set; } + public string ChannelId { get; set; } + public string Hash { get; set; } + + public FeedItem NormalizeDescription() + { + if (Description == null) return this; + + Description = HttpUtility.HtmlDecode(Description); + Description = Regex.Replace(Description, @"\", "", RegexOptions.IgnoreCase); + Description = Regex.Replace(Description, @"\", "", RegexOptions.IgnoreCase); + Description = Regex.Replace(Description, @"\", "", RegexOptions.IgnoreCase); + Description = Regex.Replace(Description, @"\", "", RegexOptions.IgnoreCase); + Description = Regex.Replace(Description, @"\", "", RegexOptions.IgnoreCase); + return this; + } } \ No newline at end of file diff --git a/src/Influunt.Feed/Entity/User.cs b/src/Influunt.Feed/Entity/User.cs index 0e1f08f..848a868 100644 --- a/src/Influunt.Feed/Entity/User.cs +++ b/src/Influunt.Feed/Entity/User.cs @@ -1,35 +1,34 @@ -using System; -using System.Linq; -using System.Security.Claims; -using Skidbladnir.Repository.Abstractions; - -namespace Influunt.Feed.Entity -{ - public class User : IHasId - { - public string Email { get; set; } - public string Name { get; set; } - public string AuthProvider { get; set; } - public string Id { get; set; } - public DateTime LastActivity { get; set; } - - public bool IsNullable() - { - if (string.IsNullOrWhiteSpace(Email) && string.IsNullOrWhiteSpace(Name)) - return true; - return false; - } - - public static User FromIdentity(ClaimsPrincipal principal) - { - var user = new User - { - Email = principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value, - AuthProvider = principal.Identity.AuthenticationType, - Name = principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value - }; - - return user.IsNullable() ? null : user; - } - } +using System; +using System.Linq; +using System.Security.Claims; +using Skidbladnir.Repository.Abstractions; + +namespace Influunt.Feed.Entity; + +public class User : IHasId +{ + public string Email { get; set; } + public string Name { get; set; } + public string AuthProvider { get; set; } + public string Id { get; set; } + public DateTime LastActivity { get; set; } + + public bool IsNullable() + { + if (string.IsNullOrWhiteSpace(Email) && string.IsNullOrWhiteSpace(Name)) + return true; + return false; + } + + public static User FromIdentity(ClaimsPrincipal principal) + { + var user = new User + { + Email = principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value, + AuthProvider = principal.Identity.AuthenticationType, + Name = principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value + }; + + return user.IsNullable() ? null : user; + } } \ No newline at end of file diff --git a/src/Influunt.Feed/Extensions/HashExtensions.cs b/src/Influunt.Feed/Extensions/HashExtensions.cs index e9de9a4..37335ec 100644 --- a/src/Influunt.Feed/Extensions/HashExtensions.cs +++ b/src/Influunt.Feed/Extensions/HashExtensions.cs @@ -3,22 +3,21 @@ using Force.Crc32; using Influunt.Feed.Entity; -namespace Influunt.Feed.Services +namespace Influunt.Feed.Services; + +public static class HashExtensions { - public static class HashExtensions - { - private static readonly Crc32Algorithm _crc32 = new Crc32Algorithm(); + private static readonly Crc32Algorithm _crc32 = new Crc32Algorithm(); - public static string ComputeHash(this FeedItem item) - { - var data = $"{item.Title}.{item.Description}.{item.ChannelId}"; - return data.ComputeHash(); - } + public static string ComputeHash(this FeedItem item) + { + var data = $"{item.Title}.{item.Description}.{item.ChannelId}"; + return data.ComputeHash(); + } - public static string ComputeHash(this string data){ - var bytes = Encoding.UTF8.GetBytes(data); - var hash = _crc32.ComputeHash(bytes); - return BitConverter.ToString(hash).Replace("-", string.Empty); - } + public static string ComputeHash(this string data){ + var bytes = Encoding.UTF8.GetBytes(data); + var hash = _crc32.ComputeHash(bytes); + return BitConverter.ToString(hash).Replace("-", string.Empty); } } \ No newline at end of file diff --git a/src/Influunt.Feed/Extensions/IQueryableExtensions.cs b/src/Influunt.Feed/Extensions/IQueryableExtensions.cs index 2b38e36..7cb457a 100644 --- a/src/Influunt.Feed/Extensions/IQueryableExtensions.cs +++ b/src/Influunt.Feed/Extensions/IQueryableExtensions.cs @@ -1,13 +1,12 @@ -using System.Linq; -using Influunt.Feed.Entity; - -namespace Influunt.Feed.Services -{ - public static class IQueryableExtensions - { - public static IQueryable GetChunkedFeed(this IQueryable feed, int? offset, int count) - { - return offset == null ? feed : feed.Skip(offset.Value).Take(count); - } - } +using System.Linq; +using Influunt.Feed.Entity; + +namespace Influunt.Feed.Services; + +public static class IQueryableExtensions +{ + public static IQueryable GetChunkedFeed(this IQueryable feed, int? offset, int count) + { + return offset == null ? feed : feed.Skip(offset.Value).Take(count); + } } \ No newline at end of file diff --git a/src/Influunt.Feed/IChannelService.cs b/src/Influunt.Feed/IChannelService.cs index 890b3cf..fa97684 100644 --- a/src/Influunt.Feed/IChannelService.cs +++ b/src/Influunt.Feed/IChannelService.cs @@ -2,48 +2,47 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace Influunt.Feed +namespace Influunt.Feed; + +/// +/// Channel service +/// +public interface IChannelService { /// - /// Channel service + /// Get channel by id /// - public interface IChannelService - { - /// - /// Get channel by id - /// - /// - /// - Task Get(string id); + /// + /// + Task Get(string id); - /// - /// Add chammel - /// - /// - /// - Task Add(FeedChannel channel); + /// + /// Add chammel + /// + /// + /// + Task Add(FeedChannel channel); - /// - /// Remove channel - /// - /// - /// - /// - Task Remove(User user, FeedChannel channel); + /// + /// Remove channel + /// + /// + /// + /// + Task Remove(User user, FeedChannel channel); - /// - /// Update channel - /// - /// - /// - /// - Task Update(User user, FeedChannel channel); + /// + /// Update channel + /// + /// + /// + /// + Task Update(User user, FeedChannel channel); - /// - /// Get user channels - /// - /// - /// - Task> GetUserChannels(User user); - } + /// + /// Get user channels + /// + /// + /// + Task> GetUserChannels(User user); } \ No newline at end of file diff --git a/src/Influunt.Feed/IFavoriteFeedService.cs b/src/Influunt.Feed/IFavoriteFeedService.cs index 3421074..dff1d53 100644 --- a/src/Influunt.Feed/IFavoriteFeedService.cs +++ b/src/Influunt.Feed/IFavoriteFeedService.cs @@ -2,32 +2,31 @@ using System.Threading.Tasks; using Influunt.Feed.Entity; -namespace Influunt.Feed +namespace Influunt.Feed; + +/// +/// Favorite feed service +/// +public interface IFavoriteFeedService { /// - /// Favorite feed service + /// Add item to favorites /// - public interface IFavoriteFeedService - { - /// - /// Add item to favorites - /// - /// - /// - /// - Task Add(User user, FeedItem favorite); + /// + /// + /// + Task Add(User user, FeedItem favorite); - /// - /// Remove from favorites - /// - /// - /// - /// - Task Remove(User user, string id); + /// + /// Remove from favorites + /// + /// + /// + /// + Task Remove(User user, string id); - /// - /// Get all favorites from user - /// - Task> GetUserFavorites(User user, int? offset); - } + /// + /// Get all favorites from user + /// + Task> GetUserFavorites(User user, int? offset); } \ No newline at end of file diff --git a/src/Influunt.Feed/IFeedService.cs b/src/Influunt.Feed/IFeedService.cs index b27d8ef..1229f71 100644 --- a/src/Influunt.Feed/IFeedService.cs +++ b/src/Influunt.Feed/IFeedService.cs @@ -2,46 +2,45 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace Influunt.Feed +namespace Influunt.Feed; + +/// +/// User feed service +/// +public interface IFeedService { /// - /// User feed service + /// Get user feed /// - public interface IFeedService - { - /// - /// Get user feed - /// - /// - /// - /// - /// - Task> GetFeed(User user, int? offset = null, int count = 10); + /// + /// + /// + /// + Task> GetFeed(User user, int? offset = null, int count = 10); - /// - /// Get feed by user and channel - /// - /// - /// - /// - /// - /// - Task> GetFeed(User user, FeedChannel channel, int? offset = null, int count = 10); + /// + /// Get feed by user and channel + /// + /// + /// + /// + /// + /// + Task> GetFeed(User user, FeedChannel channel, int? offset = null, int count = 10); - /// - /// Remove all feed items owned to this user and channel - /// - /// - /// - /// - Task RemoveFeedByChannel(User user, FeedChannel channel); + /// + /// Remove all feed items owned to this user and channel + /// + /// + /// + /// + Task RemoveFeedByChannel(User user, FeedChannel channel); - /// - /// Try add new posts in user feed - /// - /// - /// - /// - Task TryAddToFeed(User user, IEnumerable newFeedItems, FeedChannel channel = null); - } + /// + /// Try add new posts in user feed + /// + /// + /// + /// + Task TryAddToFeed(User user, IEnumerable newFeedItems, FeedChannel channel = null); } \ No newline at end of file diff --git a/src/Influunt.Feed/IFeedSourceProvider.cs b/src/Influunt.Feed/IFeedSourceProvider.cs index e47d33d..de30b38 100644 --- a/src/Influunt.Feed/IFeedSourceProvider.cs +++ b/src/Influunt.Feed/IFeedSourceProvider.cs @@ -2,23 +2,22 @@ using System.Threading.Tasks; using Influunt.Feed.Entity; -namespace Influunt.Feed +namespace Influunt.Feed; + +public interface IFeedSourceProvider { - public interface IFeedSourceProvider - { - /// - /// Get remote feed - /// - /// - /// - Task> GetRemoteFeed(FeedChannel channel); + /// + /// Get remote feed + /// + /// + /// + Task> GetRemoteFeed(FeedChannel channel); - /// - /// Can this provider process this channel - /// - /// - /// - bool CanProcessChannel(FeedChannel channel); - } + /// + /// Can this provider process this channel + /// + /// + /// + bool CanProcessChannel(FeedChannel channel); } \ No newline at end of file diff --git a/src/Influunt.Feed/IUserService.cs b/src/Influunt.Feed/IUserService.cs index 5daaacf..446fc76 100644 --- a/src/Influunt.Feed/IUserService.cs +++ b/src/Influunt.Feed/IUserService.cs @@ -1,59 +1,58 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Influunt.Feed.Entity; - -namespace Influunt.Feed -{ - /// - /// Сервис работы с пользователями - /// - public interface IUserService - { - /// - /// Получение списка пользователей - /// - /// - Task> GetUsers(); - - /// - /// Получение текущего пользователя - /// - /// - Task GetCurrentUser(); - - /// - /// Получение пользователя по ID - /// - /// - /// - Task GetUserById(string id); - - /// - /// Получение пользователя по email - /// - /// - /// - Task GetUserByEmail(string email); - - /// - /// Добавление пользователя - /// - /// - /// - Task Add(User user); - - /// - /// Обновление информации пользователя - /// - /// - /// - Task Update(User updatedUser); - - /// - /// Удаление пользователя - /// - /// - /// - Task Remove(User user); - } +using System.Collections.Generic; +using System.Threading.Tasks; +using Influunt.Feed.Entity; + +namespace Influunt.Feed; + +/// +/// Сервис работы с пользователями +/// +public interface IUserService +{ + /// + /// Получение списка пользователей + /// + /// + Task> GetUsers(); + + /// + /// Получение текущего пользователя + /// + /// + Task GetCurrentUser(); + + /// + /// Получение пользователя по ID + /// + /// + /// + Task GetUserById(string id); + + /// + /// Получение пользователя по email + /// + /// + /// + Task GetUserByEmail(string email); + + /// + /// Добавление пользователя + /// + /// + /// + Task Add(User user); + + /// + /// Обновление информации пользователя + /// + /// + /// + Task Update(User updatedUser); + + /// + /// Удаление пользователя + /// + /// + /// + Task Remove(User user); } \ No newline at end of file diff --git a/src/Influunt.Feed/Influunt.Feed.csproj b/src/Influunt.Feed/Influunt.Feed.csproj index b3977ad..c419f55 100644 --- a/src/Influunt.Feed/Influunt.Feed.csproj +++ b/src/Influunt.Feed/Influunt.Feed.csproj @@ -1,18 +1,18 @@ - - - - netstandard2.0 - Klabukov Erik - Influunt - Influunt - Simple Rss Agregator - Klabukov Erik - - - - - - - - - - + + + + net8 + Klabukov Erik + Influunt + Influunt - Simple Rss Agregator + Klabukov Erik + + + + + + + + + + diff --git a/src/Influunt.Host/Influunt.Host.csproj b/src/Influunt.Host/Influunt.Host.csproj index 71fbed4..7f04cef 100644 --- a/src/Influunt.Host/Influunt.Host.csproj +++ b/src/Influunt.Host/Influunt.Host.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net8 ClientApp\ $(DefaultItemExcludes);$(SpaRoot)node_modules\** f7b6a3ab-5dd3-487c-8763-0c114addb57b @@ -21,10 +21,10 @@ - - - - + + + + diff --git a/src/Influunt.Host/Program.cs b/src/Influunt.Host/Program.cs index c221e13..a5a52d7 100644 --- a/src/Influunt.Host/Program.cs +++ b/src/Influunt.Host/Program.cs @@ -1,41 +1,72 @@ -using System.Threading.Tasks; using Influunt.Feed.Crawler; +using Influunt.Host; using Influunt.Host.Configurations; using Influunt.Storage; -using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.AspNetCore.SpaServices; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Skidbladnir.Modules; +using VueCliMiddleware; -namespace Influunt.Host +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddOptions(); +builder.Services.Configure(builder.Configuration.GetSection("FeedCrawler")); +var crawlerEnabled = builder.Configuration.GetSection("FeedCrawler:Enabled").Get(); +if (crawlerEnabled) + builder.Services.AddHostedService(); + +builder.Services.AddSkidbladnirModules(configuration => +{ + var storageConfiguration = builder.Configuration.GetSection("ConnectionStrings:Mongo").Get(); + configuration.Add(storageConfiguration); + var redisConfiguration = builder.Configuration.GetSection("ConnectionStrings:Redis").Get(); + configuration.Add(redisConfiguration); +}, builder.Configuration); + +var app = builder.Build(); + +var forwardedHeadersOptions = new ForwardedHeadersOptions { - internal class Program + ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto, + RequireHeaderSymmetry = false +}; +forwardedHeadersOptions.KnownNetworks.Clear(); +forwardedHeadersOptions.KnownProxies.Clear(); +app.UseForwardedHeaders(forwardedHeadersOptions); + +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(c => { - public static Task Main(string[] args) - { - return CreateHostBuilder(args).Build().RunAsync(); - } - - private static IHostBuilder CreateHostBuilder(string[] args) => - Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }) - .ConfigureServices((ctx, services) => { - services.AddOptions(); - services.Configure(ctx.Configuration.GetSection("FeedCrawler")); - var crawlerEnabled = ctx.Configuration.GetSection("FeedCrawler:Enabled").Get(); - if(crawlerEnabled) - services.AddHostedService(); - }) - .UseSkidbladnirModules(configuration => - { - var storageConfiguration = configuration.AppConfiguration.GetSection("ConnectionStrings:Mongo").Get(); - configuration.Add(storageConfiguration); - var redisConfiguration = configuration.AppConfiguration.GetSection("ConnectionStrings:Redis").Get(); - configuration.Add(redisConfiguration); - }); - } + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Influunt API"); + }); + app.UseDeveloperExceptionPage(); } + +app.UseSpaStaticFiles(); +app.UseAuthentication(); +app.UseRouting(); +app.UseAuthorization(); +app.MapControllers(); + +if (app.Environment.IsDevelopment()) +{ + app.MapToVueCliProxy( + "{*path}", + new SpaOptions { SourcePath = "ClientApp" }, + npmScript: "serve", + regex: "Compiled successfully", + forceKill: true + ); +} + +app.UseSpa(spa => +{ + spa.Options.SourcePath = "ClientApp"; +}); + +app.Run(); \ No newline at end of file diff --git a/src/Influunt.Host/Startup.cs b/src/Influunt.Host/Startup.cs deleted file mode 100644 index b23b98b..0000000 --- a/src/Influunt.Host/Startup.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpOverrides; -using Microsoft.AspNetCore.SpaServices; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using VueCliMiddleware; - -namespace Influunt.Host -{ - /// - /// Startup class - /// - public class Startup - { - /// - /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - /// - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - var forwardedHeadersOptions = new ForwardedHeadersOptions - { - ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto, - RequireHeaderSymmetry = false - }; - forwardedHeadersOptions.KnownNetworks.Clear(); - forwardedHeadersOptions.KnownProxies.Clear(); - - app.UseForwardedHeaders(forwardedHeadersOptions); - if (env.IsDevelopment()) - { - app.UseSwagger(); - app.UseSwaggerUI(c => - { - c.SwaggerEndpoint("/swagger/v1/swagger.json", "Influunt API"); - }); - app.UseDeveloperExceptionPage(); - } - - app.UseSpaStaticFiles(); - - app.UseAuthentication(); - - app.UseRouting(); - - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - - if (env.IsDevelopment()) - { - endpoints.MapToVueCliProxy( - "{*path}", - new SpaOptions { SourcePath = "ClientApp"}, - npmScript: "serve", - regex: "Compiled successfully", - forceKill: true - ); - } - }); - - app.UseSpa(spa => - { - spa.Options.SourcePath = "ClientApp"; - }); - } - } -} \ No newline at end of file diff --git a/src/Influunt.Host/StartupModule.cs b/src/Influunt.Host/StartupModule.cs index 6c61e87..0e1aa32 100644 --- a/src/Influunt.Host/StartupModule.cs +++ b/src/Influunt.Host/StartupModule.cs @@ -1,43 +1,42 @@ -using System; -using Influunt.Feed.Rss; -using Influunt.Host.Configurations; -using Influunt.Host.Services; -using Influunt.Storage; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Skidbladnir.Caching.Distributed.MongoDB; -using Skidbladnir.DataProtection.MongoDb; -using Skidbladnir.Modules; - -namespace Influunt.Host -{ - public class StartupModule : Module - { - public override Type[] DependsModules => new[] {typeof(WebModule), typeof(StorageModule), typeof(RssModule)}; - - public override void Configure(IServiceCollection services) - { - services.AddDataProtection() - .PersistKeysToMongoDb(Configuration.AppConfiguration["ConnectionStrings:Mongo:ConnectionString"]); - ConfigureDistributedCache(services); - services.TryAddSingleton(); - } - - private void ConfigureDistributedCache(IServiceCollection services) - { - var redisConfiguration = Configuration.Get(); - if(string.IsNullOrWhiteSpace(redisConfiguration?.ConnectionString)) - { - services.AddMongoDistributedCache(Configuration.AppConfiguration["ConnectionStrings:Mongo:ConnectionString"]); - } - else - { - services.AddStackExchangeRedisCache(c => c.Configuration = redisConfiguration.ConnectionString) - .Decorate(); - } - - } - } +using System; +using Influunt.Feed.Rss; +using Influunt.Host.Configurations; +using Influunt.Host.Services; +using Influunt.Storage; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Skidbladnir.Caching.Distributed.MongoDB; +using Skidbladnir.DataProtection.MongoDb; +using Skidbladnir.Modules; + +namespace Influunt.Host; + +public class StartupModule : Module +{ + public override Type[] DependsModules => new[] {typeof(WebModule), typeof(StorageModule), typeof(RssModule)}; + + public override void Configure(IServiceCollection services) + { + services.AddDataProtection() + .PersistKeysToMongoDb(Configuration.AppConfiguration["ConnectionStrings:Mongo:ConnectionString"]); + ConfigureDistributedCache(services); + services.TryAddSingleton(); + } + + private void ConfigureDistributedCache(IServiceCollection services) + { + var redisConfiguration = Configuration.Get(); + if(string.IsNullOrWhiteSpace(redisConfiguration?.ConnectionString)) + { + services.AddMongoDistributedCache(Configuration.AppConfiguration["ConnectionStrings:Mongo:ConnectionString"]); + } + else + { + services.AddStackExchangeRedisCache(c => c.Configuration = redisConfiguration.ConnectionString) + .Decorate(); + } + + } } \ No newline at end of file diff --git a/src/Influunt.Host/WebModule.cs b/src/Influunt.Host/WebModule.cs index e43a33f..6f278aa 100644 --- a/src/Influunt.Host/WebModule.cs +++ b/src/Influunt.Host/WebModule.cs @@ -11,62 +11,61 @@ using Microsoft.OpenApi.Models; using Skidbladnir.Modules; -namespace Influunt.Host +namespace Influunt.Host; + +public class WebModule : Module { - public class WebModule : Module + public override void Configure(IServiceCollection services) { - public override void Configure(IServiceCollection services) + services + .AddDataProtection() + .SetApplicationName("Influunt"); + services.AddControllers(); + services.Configure(options => + { + options.ForwardedHeaders = + ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; + }); + services.Configure(options => { - services - .AddDataProtection() - .SetApplicationName("Influunt"); - services.AddControllers(); - services.Configure(options => + + options.CheckConsentNeeded = context => true; + options.MinimumSameSitePolicy = SameSiteMode.Lax; + }); + + services.AddAuthentication(options => { - options.ForwardedHeaders = - ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; - }); - services.Configure(options => + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme; + }) + .AddCookie() + .AddGoogle(options => { + var googleAuthNSection = + Configuration.AppConfiguration.GetSection("Authentication:Google"); - options.CheckConsentNeeded = context => true; - options.MinimumSameSitePolicy = SameSiteMode.Lax; + options.ClientId = googleAuthNSection["ClientId"]; + options.ClientSecret = googleAuthNSection["ClientSecret"]; + options.CorrelationCookie.SameSite = SameSiteMode.Lax; }); - - services.AddAuthentication(options => - { - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme; - }) - .AddCookie() - .AddGoogle(options => - { - IConfigurationSection googleAuthNSection = - Configuration.AppConfiguration.GetSection("Authentication:Google"); - - options.ClientId = googleAuthNSection["ClientId"]; - options.ClientSecret = googleAuthNSection["ClientSecret"]; - options.CorrelationCookie.SameSite = SameSiteMode.Lax; - }); - // In production, the Vue files will be served from this directory - services.AddSpaStaticFiles(configuration => + // In production, the Vue files will be served from this directory + services.AddSpaStaticFiles(configuration => + { + configuration.RootPath = "ClientApp/dist"; + }); + services + .AddSwaggerGen(c => { - configuration.RootPath = "ClientApp/dist"; - }); - services - .AddSwaggerGen(c => + c.SwaggerDoc("v1", new OpenApiInfo { - c.SwaggerDoc("v1", new OpenApiInfo - { - Version = "v1", - Title = "Influunt API", - Description = "Influunt (Rss agregator) Api" - }); - c.CustomSchemaIds(type => type.FullName); - var filePath = Path.Combine(AppContext.BaseDirectory, "Influunt.Host.xml"); - if (File.Exists(filePath)) - c.IncludeXmlComments(filePath); + Version = "v1", + Title = "Influunt API", + Description = "Influunt (Rss agregator) Api" }); - } + c.CustomSchemaIds(type => type.FullName); + var filePath = Path.Combine(AppContext.BaseDirectory, "Influunt.Host.xml"); + if (File.Exists(filePath)) + c.IncludeXmlComments(filePath); + }); } } \ No newline at end of file diff --git a/src/Influunt.Storage/Influunt.Storage.csproj b/src/Influunt.Storage/Influunt.Storage.csproj index e03b628..ed239eb 100644 --- a/src/Influunt.Storage/Influunt.Storage.csproj +++ b/src/Influunt.Storage/Influunt.Storage.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net8 Klabukov Erik Influunt Influunt - Simple Rss Agregator diff --git a/src/Influunt.Storage/StorageConfiguration.cs b/src/Influunt.Storage/StorageConfiguration.cs index 6ff1ada..402a7c9 100644 --- a/src/Influunt.Storage/StorageConfiguration.cs +++ b/src/Influunt.Storage/StorageConfiguration.cs @@ -1,7 +1,6 @@ -namespace Influunt.Storage -{ - public class StorageConfiguration - { - public string ConnectionString { get; set; } - } +namespace Influunt.Storage; + +public class StorageConfiguration +{ + public string ConnectionString { get; set; } } \ No newline at end of file diff --git a/src/Influunt.Storage/StorageModule.cs b/src/Influunt.Storage/StorageModule.cs index 241445d..76c4879 100644 --- a/src/Influunt.Storage/StorageModule.cs +++ b/src/Influunt.Storage/StorageModule.cs @@ -12,112 +12,111 @@ using Skidbladnir.Modules; using Skidbladnir.Repository.MongoDB; -namespace Influunt.Storage +namespace Influunt.Storage; + +public class StorageModule : RunnableModule { - public class StorageModule : RunnableModule + public override Type[] DependsModules => new []{ typeof(HashMigrationWorkerModule)}; + public override void Configure(IServiceCollection services) { - public override Type[] DependsModules => new []{ typeof(HashMigrationWorkerModule)}; - public override void Configure(IServiceCollection services) + // Register conventions + var pack = new ConventionPack { - // Register conventions - var pack = new ConventionPack - { - new IgnoreIfDefaultConvention(true), - new IgnoreExtraElementsConvention(true), - }; + new IgnoreIfDefaultConvention(true), + new IgnoreExtraElementsConvention(true), + }; - ConventionRegistry.Register("Influunt", pack, t => true); + ConventionRegistry.Register("Influunt", pack, t => true); - var storageCfg = Configuration.Get(); + var storageCfg = Configuration.Get(); - //Database - services.AddMongoDbContext(builder => - { - builder.UseConnectionString(storageCfg.ConnectionString); - builder.AddEntity(); - builder.AddEntity(); - builder.AddEntity(); - builder.AddEntity(); - }); + //Database + services.AddMongoDbContext(builder => + { + builder.UseConnectionString(storageCfg.ConnectionString); + builder.AddEntity(); + builder.AddEntity(); + builder.AddEntity(); + builder.AddEntity(); + }); - //Services - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - } + //Services + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + } - public override async Task StartAsync(IServiceProvider provider, CancellationToken cancellationToken) - { - var logger = provider.GetService>(); - var baseMongoContext = provider.GetService(); + public override async Task StartAsync(IServiceProvider provider, CancellationToken cancellationToken) + { + var logger = provider.GetService>(); + var baseMongoContext = provider.GetService(); - logger?.LogInformation("Initialize indices"); - try - { - await CreateUserIndexes(baseMongoContext); - await CreateChannelIndexes(baseMongoContext); - await CreateFavoriteIndexes(baseMongoContext); - await CreateFeedIndexes(baseMongoContext); - } - catch (Exception e) - { - logger?.LogError(e, "Can't create indices"); - } + logger?.LogInformation("Initialize indices"); + try + { + await CreateUserIndexes(baseMongoContext); + await CreateChannelIndexes(baseMongoContext); + await CreateFavoriteIndexes(baseMongoContext); + await CreateFeedIndexes(baseMongoContext); + } + catch (Exception e) + { + logger?.LogError(e, "Can't create indices"); } + } - private async Task CreateFeedIndexes(BaseMongoDbContext baseMongoContext) + private async Task CreateFeedIndexes(BaseMongoDbContext baseMongoContext) + { + var collection = baseMongoContext.GetCollection(); + var userIdAndChannelIdAndPubDateDefinition = Builders.IndexKeys + .Ascending(x => x.UserId) + .Ascending(x => x.ChannelId) + .Descending(x => x.PubDate); + await collection.Indexes.CreateOneAsync(new CreateIndexModel(userIdAndChannelIdAndPubDateDefinition, new CreateIndexOptions() { - var collection = baseMongoContext.GetCollection(); - var userIdAndChannelIdAndPubDateDefinition = Builders.IndexKeys - .Ascending(x => x.UserId) - .Ascending(x => x.ChannelId) - .Descending(x => x.PubDate); - await collection.Indexes.CreateOneAsync(new CreateIndexModel(userIdAndChannelIdAndPubDateDefinition, new CreateIndexOptions() - { - Background = true - })); + Background = true + })); - var userIdAndHashDefinition = Builders.IndexKeys - .Ascending(x => x.UserId) - .Ascending(x => x.Hash); - await collection.Indexes.CreateOneAsync(new CreateIndexModel(userIdAndHashDefinition, new CreateIndexOptions() - { - Background = true - })); - } + var userIdAndHashDefinition = Builders.IndexKeys + .Ascending(x => x.UserId) + .Ascending(x => x.Hash); + await collection.Indexes.CreateOneAsync(new CreateIndexModel(userIdAndHashDefinition, new CreateIndexOptions() + { + Background = true + })); + } - private async Task CreateFavoriteIndexes(BaseMongoDbContext baseMongoContext) + private async Task CreateFavoriteIndexes(BaseMongoDbContext baseMongoContext) + { + var collection = baseMongoContext.GetCollection(); + var userIdAndPubDateDefinition = Builders.IndexKeys + .Ascending(x => x.UserId) + .Descending(x => x.PubDate); + await collection.Indexes.CreateOneAsync(new CreateIndexModel(userIdAndPubDateDefinition, new CreateIndexOptions() { - var collection = baseMongoContext.GetCollection(); - var userIdAndPubDateDefinition = Builders.IndexKeys - .Ascending(x => x.UserId) - .Descending(x => x.PubDate); - await collection.Indexes.CreateOneAsync(new CreateIndexModel(userIdAndPubDateDefinition, new CreateIndexOptions() - { - Background = true - })); - } + Background = true + })); + } - private async Task CreateChannelIndexes(BaseMongoDbContext baseMongoContext) + private async Task CreateChannelIndexes(BaseMongoDbContext baseMongoContext) + { + var collection = baseMongoContext.GetCollection(); + var userIdKeyDefinition = Builders.IndexKeys.Ascending(x => x.UserId); + await collection.Indexes.CreateOneAsync(new CreateIndexModel(userIdKeyDefinition, new CreateIndexOptions() { - var collection = baseMongoContext.GetCollection(); - var userIdKeyDefinition = Builders.IndexKeys.Ascending(x => x.UserId); - await collection.Indexes.CreateOneAsync(new CreateIndexModel(userIdKeyDefinition, new CreateIndexOptions() - { - Background = true - })); - } + Background = true + })); + } - private async Task CreateUserIndexes(BaseMongoDbContext baseMongoContext) + private async Task CreateUserIndexes(BaseMongoDbContext baseMongoContext) + { + var collection = baseMongoContext.GetCollection(); + var emailKeyDefinition = Builders.IndexKeys.Ascending(x => x.Email); + await collection.Indexes.CreateOneAsync(new CreateIndexModel(emailKeyDefinition, new CreateIndexOptions() { - var collection = baseMongoContext.GetCollection(); - var emailKeyDefinition = Builders.IndexKeys.Ascending(x => x.Email); - await collection.Indexes.CreateOneAsync(new CreateIndexModel(emailKeyDefinition, new CreateIndexOptions() - { - Unique = true, - Background = true - })); - } + Unique = true, + Background = true + })); } } \ No newline at end of file From abae27aca67b694f58004d54f17ddecc0fcaff8b Mon Sep 17 00:00:00 2001 From: Klabukov Erik Date: Wed, 10 Jan 2024 23:33:28 +0500 Subject: [PATCH 2/4] Suggests and file scoped namespaces Signed-off-by: Klabukov Erik --- .vscode/settings.json | 2 + src/Influunt.Feed.Rss/Extensions.cs | 34 ++- .../RssFeedSourceProvider.cs | 5 +- .../Crawler/FeedCrawlerBackgroundWorker.cs | 6 +- .../Configurations/RedisConfiguration.cs | 11 +- .../Controllers/AccountController.cs | 208 ++++++++-------- .../Controllers/ChannelController.cs | 197 ++++++++------- .../Controllers/FavoriteController.cs | 132 +++++----- .../Controllers/FeedController.cs | 95 ++++--- .../Controllers/VersionController.cs | 49 ++-- .../Services/RedisCacheFallbackDecorator.cs | 231 +++++++++--------- src/Influunt.Host/StartupModule.cs | 2 +- .../ViewModels/FavoriteFeedItemViewModel.cs | 65 +++-- .../ViewModels/FeedChannelViewModel.cs | 98 ++++---- .../ViewModels/FeedItemViewModel.cs | 57 +++-- .../ViewModels/MappingExtensions.cs | 189 +++++++------- src/Influunt.Host/ViewModels/UserViewModel.cs | 31 ++- src/Influunt.Host/WebModule.cs | 3 +- .../Entity/FavoriteFeedItemMap.cs | 12 +- src/Influunt.Storage/Entity/FeedChannelMap.cs | 27 +- src/Influunt.Storage/Entity/FeedItemMap.cs | 13 +- src/Influunt.Storage/Entity/UserMap.cs | 35 ++- .../Services/ChannelService.cs | 75 +++--- .../Services/FavoriteService.cs | 81 +++--- src/Influunt.Storage/Services/FeedService.cs | 124 +++++----- .../Services/HashMigrationWorkerModule.cs | 52 ++-- src/Influunt.Storage/Services/UserService.cs | 96 ++++---- src/Influunt.Storage/StorageModule.cs | 10 +- 28 files changed, 957 insertions(+), 983 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index acf2f15..7e4bf2c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,7 @@ { "cSpell.words": [ + "Bson", + "Catched", "Influunt", "Skidbladnir" ] diff --git a/src/Influunt.Feed.Rss/Extensions.cs b/src/Influunt.Feed.Rss/Extensions.cs index 485436d..6f42de7 100644 --- a/src/Influunt.Feed.Rss/Extensions.cs +++ b/src/Influunt.Feed.Rss/Extensions.cs @@ -62,27 +62,25 @@ public static List FeedFromRss(this string xml) public static List FeedFromAtomRss(this string xml) { - using (var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(xml ?? ""))) + using var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(xml ?? "")); + var ser = new XmlSerializer(typeof(Feed)); + var rssBody = (Feed)ser.Deserialize(memoryStream); + return rssBody.Entry.Select(rssItem => { - var ser = new XmlSerializer(typeof(Feed)); - var rssBody = (Feed)ser.Deserialize(memoryStream); - return rssBody.Entry.Select(rssItem => + var item = new FeedItem { - var item = new FeedItem - { - Title = rssItem.Title, - Description = string.IsNullOrEmpty(rssItem.Content?.Text) - ? rssItem.Summary - : rssItem.Content.Text, - PubDate = DateTime.UtcNow, - Link = rssItem.Link?.ToString(), - }; + Title = rssItem.Title, + Description = string.IsNullOrEmpty(rssItem.Content?.Text) + ? rssItem.Summary + : rssItem.Content.Text, + PubDate = DateTime.UtcNow, + Link = rssItem.Link?.ToString(), + }; - if (DateTime.TryParse(rssItem.Published, out var pubDate)) - item.PubDate = pubDate; + if (DateTime.TryParse(rssItem.Published, out var pubDate)) + item.PubDate = pubDate; - return item.NormalizeDescription(); - }).ToList(); - } + return item.NormalizeDescription(); + }).ToList(); } } \ No newline at end of file diff --git a/src/Influunt.Feed.Rss/RssFeedSourceProvider.cs b/src/Influunt.Feed.Rss/RssFeedSourceProvider.cs index 59b1676..32f6c13 100644 --- a/src/Influunt.Feed.Rss/RssFeedSourceProvider.cs +++ b/src/Influunt.Feed.Rss/RssFeedSourceProvider.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Threading.Tasks; using Influunt.Feed.Entity; @@ -42,11 +41,11 @@ public void Dispose() private async Task> GetFeedFromChannelCached(FeedChannel channel) { var channelFeed = await _distributedCache.GetAsync>($"channel_url_{channel.Url}"); - if (channelFeed != null && channelFeed.Count != 0) + if (channelFeed is not null && channelFeed.Count != 0) return channelFeed; channelFeed = (await _rssClient.GetFeed(channel)).AsList(); - if (channelFeed.Any()) + if (channelFeed.Count != 0) await _distributedCache.SetAsync($"channel_url_{channel.Url}", channelFeed, new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) diff --git a/src/Influunt.Feed/Crawler/FeedCrawlerBackgroundWorker.cs b/src/Influunt.Feed/Crawler/FeedCrawlerBackgroundWorker.cs index 6907852..68a4407 100644 --- a/src/Influunt.Feed/Crawler/FeedCrawlerBackgroundWorker.cs +++ b/src/Influunt.Feed/Crawler/FeedCrawlerBackgroundWorker.cs @@ -51,7 +51,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) break; _logger.LogError(e, "Error in Feed Crawler Background Worker when fetch feeds."); } - await Task.Delay(_options.FetchInterval); + await Task.Delay(_options.FetchInterval, stoppingToken); } _logger.LogInformation("Feed Crawler Background Worker is stopping."); @@ -66,7 +66,7 @@ private async Task InnerExecute(CancellationToken token) { token.ThrowIfCancellationRequested(); var channels = await Try.DoAsync(() => _channelService.GetUserChannels(user)); - if (channels == null) + if (channels is null) continue; try { @@ -86,7 +86,7 @@ private async Task FetchFeedFromChannel(FeedChannel channel, User user, Cancella { token.ThrowIfCancellationRequested(); var remoteFeedProvider = _feedSourceProviders.FirstOrDefault(x => x.CanProcessChannel(channel)); - if (remoteFeedProvider == null) + if (remoteFeedProvider is null) return; var remoteFeed = await remoteFeedProvider.GetRemoteFeed(channel); var count = await _feedService.TryAddToFeed(user, remoteFeed, channel); diff --git a/src/Influunt.Host/Configurations/RedisConfiguration.cs b/src/Influunt.Host/Configurations/RedisConfiguration.cs index 6b26877..45f7834 100644 --- a/src/Influunt.Host/Configurations/RedisConfiguration.cs +++ b/src/Influunt.Host/Configurations/RedisConfiguration.cs @@ -1,7 +1,6 @@ -namespace Influunt.Host.Configurations -{ - public class RedisConfiguration - { - public string ConnectionString { get; set; } - } +namespace Influunt.Host.Configurations; + +public class RedisConfiguration +{ + public string ConnectionString { get; set; } } \ No newline at end of file diff --git a/src/Influunt.Host/Controllers/AccountController.cs b/src/Influunt.Host/Controllers/AccountController.cs index 908f495..e3eb53f 100644 --- a/src/Influunt.Host/Controllers/AccountController.cs +++ b/src/Influunt.Host/Controllers/AccountController.cs @@ -1,106 +1,104 @@ -using System.Collections.Generic; -using System.Security.Claims; -using Influunt.Feed; -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; -using Influunt.Host.ViewModels; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Authorization; - -namespace Influunt.Host.Controllers -{ - /// - /// Account api - /// - [Route("api/[controller]")] - [ApiController] - public class AccountController : ControllerBase - { - private readonly IUserService _userService; - - /// - public AccountController(IUserService userService) - { - _userService = userService; - } - - /// - /// Returns current user profile or null - /// - /// User profile - [HttpGet("current")] - [ProducesResponseType(typeof(UserViewModel),200)] - public async Task CurrentUser() - { - var user = await _userService.GetCurrentUser(); - - return new JsonResult(user.ToModel()); - } - - /// - /// SignIn via default challenge - /// - /// Redirect to application home after signin - [HttpGet("login")] - [Authorize] - public IActionResult SignIn() - { - return Redirect("/"); - } - - /// - /// SignIn via Google challenge - /// - /// Redirect to application home after signin - [HttpGet("login/google")] - public async Task SignInGoogle() - { - var user = await _userService.GetCurrentUser(); - - if (user != null && !string.IsNullOrWhiteSpace(user.Email)) - return Redirect("/"); - - var authProperties = new AuthenticationProperties - { - IsPersistent = true - }; - return Challenge(authProperties,"Google"); - - } - - /// - /// Sing In as guest for try service - /// - /// Redirect to application home after signin - [HttpGet("login/guest")] - public async Task SignInAsGuest() - { - var claims = new List{ - new Claim(ClaimTypes.Name, "Guest"), - new Claim(ClaimTypes.Email, "guest@local"), - }; - var claimsIdentity = new ClaimsIdentity( - claims, "GuestScheme"); - - var authProperties = new AuthenticationProperties(); - await HttpContext.SignInAsync( - CookieAuthenticationDefaults.AuthenticationScheme, - new ClaimsPrincipal(claimsIdentity), authProperties); - - return Redirect("/"); - - } - - /// - /// SignOut from service - /// - /// Redirect to application home after signout - [HttpGet("[action]")] - public async Task SignOut() - { - await HttpContext.SignOutAsync(); - return Redirect("/"); - } - } +using System.Collections.Generic; +using System.Security.Claims; +using Influunt.Feed; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Influunt.Host.ViewModels; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authorization; + +namespace Influunt.Host.Controllers; + +/// +/// Account api +/// +[Route("api/[controller]")] +[ApiController] +public class AccountController : ControllerBase +{ + private readonly IUserService _userService; + + /// + public AccountController(IUserService userService) + { + _userService = userService; + } + + /// + /// Returns current user profile or null + /// + /// User profile + [HttpGet("current")] + [ProducesResponseType(typeof(UserViewModel),200)] + public async Task CurrentUser() + { + var user = await _userService.GetCurrentUser(); + + return new JsonResult(user.ToModel()); + } + + /// + /// SignIn via default challenge + /// + /// Redirect to application home after signin + [HttpGet("login")] + [Authorize] + public IActionResult SignIn() + { + return Redirect("/"); + } + + /// + /// SignIn via Google challenge + /// + /// Redirect to application home after signin + [HttpGet("login/google")] + public async Task SignInGoogle() + { + var user = await _userService.GetCurrentUser(); + + if (!string.IsNullOrWhiteSpace(user?.Email)) + return Redirect("/"); + + var authProperties = new AuthenticationProperties + { + IsPersistent = true + }; + return Challenge(authProperties,"Google"); + } + + /// + /// Sing In as guest for try service + /// + /// Redirect to application home after signin + [HttpGet("login/guest")] + public async Task SignInAsGuest() + { + var claims = new List{ + new(ClaimTypes.Name, "Guest"), + new(ClaimTypes.Email, "guest@local"), + }; + var claimsIdentity = new ClaimsIdentity( + claims, "GuestScheme"); + + var authProperties = new AuthenticationProperties(); + await HttpContext.SignInAsync( + CookieAuthenticationDefaults.AuthenticationScheme, + new ClaimsPrincipal(claimsIdentity), authProperties); + + return Redirect("/"); + + } + + /// + /// SignOut from service + /// + /// Redirect to application home after signout + [HttpGet("[action]")] + public new async Task SignOut() + { + await HttpContext.SignOutAsync(); + return Redirect("/"); + } } \ No newline at end of file diff --git a/src/Influunt.Host/Controllers/ChannelController.cs b/src/Influunt.Host/Controllers/ChannelController.cs index d3e5974..3a9cdfc 100644 --- a/src/Influunt.Host/Controllers/ChannelController.cs +++ b/src/Influunt.Host/Controllers/ChannelController.cs @@ -7,119 +7,118 @@ using System.Threading.Tasks; using Influunt.Host.ViewModels; -namespace Influunt.Host.Controllers +namespace Influunt.Host.Controllers; + +/// +/// Channels api +/// +[Route("api/[controller]")] +[ApiController] +[Authorize] +public class ChannelController : ControllerBase { + private readonly IUserService _userService; + private readonly IChannelService _channelService; + private readonly IFeedService _feedService; + + /// + public ChannelController(IUserService userService, + IChannelService channelService, + IFeedService feedService) + { + _userService = userService; + _channelService = channelService; + _feedService = feedService; + } + /// - /// Channels api + /// All user channels /// - [Route("api/[controller]")] - [ApiController] - [Authorize] - public class ChannelController : ControllerBase + /// Channels array + /// Unauthorize + [HttpGet] + public async Task> Get() { - private readonly IUserService _userService; - private readonly IChannelService _channelService; - private readonly IFeedService _feedService; - - /// - public ChannelController(IUserService userService, - IChannelService channelService, - IFeedService feedService) - { - _userService = userService; - _channelService = channelService; - _feedService = feedService; - } - - /// - /// All user channels - /// - /// Channels array - /// Unauthorize - [HttpGet] - public async Task> Get() - { - var user = await _userService.GetCurrentUser(); - return (await _channelService.GetUserChannels(user)).ToModel(); - } + var user = await _userService.GetCurrentUser(); + return (await _channelService.GetUserChannels(user)).ToModel(); + } - /// - /// Get user channel by id - /// - /// Channel id - /// Channel - /// Unauthorize - [HttpGet("{id}")] - public async Task Get(string id) - { - var user = await _userService.GetCurrentUser(); - var channel = await _channelService.Get(id); - if (channel.UserId.Equals(user.Id, StringComparison.OrdinalIgnoreCase)) - return channel.ToModel(); + /// + /// Get user channel by id + /// + /// Channel id + /// Channel + /// Unauthorize + [HttpGet("{id}")] + public async Task Get(string id) + { + var user = await _userService.GetCurrentUser(); + var channel = await _channelService.Get(id); + if (channel.UserId.Equals(user.Id, StringComparison.OrdinalIgnoreCase)) + return channel.ToModel(); - Response.StatusCode = (int) HttpStatusCode.Forbidden; - return null; - } + Response.StatusCode = (int) HttpStatusCode.Forbidden; + return null; + } - /// - /// Add channel - /// - /// - /// Ok - /// Channel validation failed - /// Unauthorize - [HttpPost] - public async Task Post([FromBody] FeedChannelViewModel channel) - { - var user = await _userService.GetCurrentUser(); - var channelEntity = channel.ToEntity(); - channelEntity.UserId = user.Id; - await _channelService.Add(channelEntity); + /// + /// Add channel + /// + /// + /// Ok + /// Channel validation failed + /// Unauthorize + [HttpPost] + public async Task Post([FromBody] FeedChannelViewModel channel) + { + var user = await _userService.GetCurrentUser(); + var channelEntity = channel.ToEntity(); + channelEntity.UserId = user.Id; + await _channelService.Add(channelEntity); - return Ok(); - } + return Ok(); + } - /// - /// Update channel - /// - /// - /// - /// OK - /// Channel validation failed - /// Unauthorize - [HttpPut("{id}")] - public async Task Put(string id, [FromBody] FeedChannelViewModel channel) - { - var user = await _userService.GetCurrentUser(); + /// + /// Update channel + /// + /// + /// + /// OK + /// Channel validation failed + /// Unauthorize + [HttpPut("{id}")] + public async Task Put(string id, [FromBody] FeedChannelViewModel channel) + { + var user = await _userService.GetCurrentUser(); - var channelInStore = await _channelService.Get(id); - if (!channelInStore.UserId.Equals(user.Id, StringComparison.OrdinalIgnoreCase)) - return Forbid(); + var channelInStore = await _channelService.Get(id); + if (!channelInStore.UserId.Equals(user.Id, StringComparison.OrdinalIgnoreCase)) + return Forbid(); - var channelEntity = channel.ToEntity(); - channelEntity.Id = channelInStore.Id; - channelEntity.UserId = channelInStore.UserId; - await _channelService.Update(user, channelEntity); - return Ok(); - } + var channelEntity = channel.ToEntity(); + channelEntity.Id = channelInStore.Id; + channelEntity.UserId = channelInStore.UserId; + await _channelService.Update(user, channelEntity); + return Ok(); + } - /// - /// Delete channel by id - /// - /// - [HttpDelete("{id}")] - public async Task Delete(string id) - { - var user = await _userService.GetCurrentUser(); - var channel = await _channelService.Get(id); - if (!channel.UserId.Equals(user.Id, StringComparison.OrdinalIgnoreCase)) - return Forbid(); - await _channelService.Remove(user, channel); + /// + /// Delete channel by id + /// + /// + [HttpDelete("{id}")] + public async Task Delete(string id) + { + var user = await _userService.GetCurrentUser(); + var channel = await _channelService.Get(id); + if (!channel.UserId.Equals(user.Id, StringComparison.OrdinalIgnoreCase)) + return Forbid(); + await _channelService.Remove(user, channel); #pragma warning disable 4014 - _feedService.RemoveFeedByChannel(user, channel); + _feedService.RemoveFeedByChannel(user, channel); #pragma warning restore 4014 - return Ok(); - } + return Ok(); } } \ No newline at end of file diff --git a/src/Influunt.Host/Controllers/FavoriteController.cs b/src/Influunt.Host/Controllers/FavoriteController.cs index 5815dd6..5948fa4 100644 --- a/src/Influunt.Host/Controllers/FavoriteController.cs +++ b/src/Influunt.Host/Controllers/FavoriteController.cs @@ -1,67 +1,65 @@ -using Influunt.Feed; -using Influunt.Feed.Entity; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using System.Collections.Generic; -using System.Threading.Tasks; -using Influunt.Host.ViewModels; - -namespace Influunt.Host.Controllers -{ - /// - /// Favorites api - /// - [Route("api/[controller]")] - [ApiController] - [Authorize] - public class FavoriteController : ControllerBase - { - private readonly IUserService _userService; - private readonly IFavoriteFeedService _favoriteFeedService; - - /// - public FavoriteController(IUserService userService, IFavoriteFeedService favoriteFeedService) - { - _userService = userService; - _favoriteFeedService = favoriteFeedService; - } - - /// - /// Get Favorites - /// - /// Favorites - /// Unauthorize - [HttpGet] - public async Task> Get([FromQuery] int? offset) - { - var user = await _userService.GetCurrentUser(); - return (await _favoriteFeedService.GetUserFavorites(user, offset)).ToModel(); - } - - // POST: api/Favorite - /// - /// Add feed to favorites - /// - /// - /// Unauthorize - [HttpPost] - public async Task Post([FromBody] FeedItemViewModel feedItem) - { - var user = await _userService.GetCurrentUser(); - await _favoriteFeedService.Add(user, feedItem.ToEntity()); - } - - // DELETE: api/Favorite/5 - /// - /// Delete favorite by id - /// - /// - /// Unauthorize - [HttpDelete("{id}")] - public async Task Delete(string id) - { - var user = await _userService.GetCurrentUser(); - await _favoriteFeedService.Remove(user, id); - } - } -} +using Influunt.Feed; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Threading.Tasks; +using Influunt.Host.ViewModels; + +namespace Influunt.Host.Controllers; + +/// +/// Favorites api +/// +[Route("api/[controller]")] +[ApiController] +[Authorize] +public class FavoriteController : ControllerBase +{ + private readonly IUserService _userService; + private readonly IFavoriteFeedService _favoriteFeedService; + + /// + public FavoriteController(IUserService userService, IFavoriteFeedService favoriteFeedService) + { + _userService = userService; + _favoriteFeedService = favoriteFeedService; + } + + /// + /// Get Favorites + /// + /// Favorites + /// Unauthorize + [HttpGet] + public async Task> Get([FromQuery] int? offset) + { + var user = await _userService.GetCurrentUser(); + return (await _favoriteFeedService.GetUserFavorites(user, offset)).ToModel(); + } + + // POST: api/Favorite + /// + /// Add feed to favorites + /// + /// + /// Unauthorize + [HttpPost] + public async Task Post([FromBody] FeedItemViewModel feedItem) + { + var user = await _userService.GetCurrentUser(); + await _favoriteFeedService.Add(user, feedItem.ToEntity()); + } + + // DELETE: api/Favorite/5 + /// + /// Delete favorite by id + /// + /// + /// Unauthorize + [HttpDelete("{id}")] + public async Task Delete(string id) + { + var user = await _userService.GetCurrentUser(); + await _favoriteFeedService.Remove(user, id); + } +} diff --git a/src/Influunt.Host/Controllers/FeedController.cs b/src/Influunt.Host/Controllers/FeedController.cs index 62c3211..d929341 100644 --- a/src/Influunt.Host/Controllers/FeedController.cs +++ b/src/Influunt.Host/Controllers/FeedController.cs @@ -5,58 +5,57 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace Influunt.Host.Controllers +namespace Influunt.Host.Controllers; + +/// +/// Feed api +/// +[Route("api/[controller]")] +[ApiController] +[Authorize] +public class FeedController : ControllerBase { + private readonly IUserService _userService; + private readonly IFeedService _feedService; + private readonly IChannelService _channelService; + + /// + public FeedController(IUserService userService, IFeedService feedService, IChannelService channelService) + { + _userService = userService; + _feedService = feedService; + _channelService = channelService; + } + /// - /// Feed api + /// Get Feed from all channels /// - [Route("api/[controller]")] - [ApiController] - [Authorize] - public class FeedController : ControllerBase + /// feed offset + /// Feed + /// Unauthorize + [HttpGet] + public async Task> Get([FromQuery] int? offset) { - private readonly IUserService _userService; - private readonly IFeedService _feedService; - private readonly IChannelService _channelService; - - /// - public FeedController(IUserService userService, IFeedService feedService, IChannelService channelService) - { - _userService = userService; - _feedService = feedService; - _channelService = channelService; - } - - /// - /// Get Feed from all channels - /// - /// feed offset - /// Feed - /// Unauthorize - [HttpGet] - public async Task> Get([FromQuery] int? offset) - { - var user = await _userService.GetCurrentUser(); - var feed = await _feedService.GetFeed(user, offset); - var userChannels = await _channelService.GetUserChannels(user); - return feed.ToModel(userChannels); - } + var user = await _userService.GetCurrentUser(); + var feed = await _feedService.GetFeed(user, offset); + var userChannels = await _channelService.GetUserChannels(user); + return feed.ToModel(userChannels); + } - // GET: api/Feed/5 - /// - /// Get feed from channel - /// - /// - /// feed offset - /// Feed - /// Unauthorize - [HttpGet("{id}")] - public async Task> Get(string id, [FromQuery] int? offset) - { - var user = await _userService.GetCurrentUser(); - var channel = await _channelService.Get(id); - var feed = await _feedService.GetFeed(user, channel, offset); - return feed.ToModel(new[] { channel }); - } + // GET: api/Feed/5 + /// + /// Get feed from channel + /// + /// + /// feed offset + /// Feed + /// Unauthorize + [HttpGet("{id}")] + public async Task> Get(string id, [FromQuery] int? offset) + { + var user = await _userService.GetCurrentUser(); + var channel = await _channelService.Get(id); + var feed = await _feedService.GetFeed(user, channel, offset); + return feed.ToModel(new[] { channel }); } } diff --git a/src/Influunt.Host/Controllers/VersionController.cs b/src/Influunt.Host/Controllers/VersionController.cs index 12da668..9e75211 100644 --- a/src/Influunt.Host/Controllers/VersionController.cs +++ b/src/Influunt.Host/Controllers/VersionController.cs @@ -1,26 +1,25 @@ -using Microsoft.AspNetCore.Mvc; -using System.Reflection; - -namespace Influunt.Host.Controllers -{ - /// - /// Version API - /// - [Route("api/[controller]")] - [ApiController] - public class VersionController : ControllerBase - { - /// - /// Get version - /// - /// Version - [HttpGet] - public IActionResult Get() - { - var version = Assembly.GetEntryAssembly()?.GetCustomAttribute() - ?.InformationalVersion; - - return new ObjectResult(new {Version = version}) {StatusCode = 200}; - } - } +using Microsoft.AspNetCore.Mvc; +using System.Reflection; + +namespace Influunt.Host.Controllers; + +/// +/// Version API +/// +[Route("api/[controller]")] +[ApiController] +public class VersionController : ControllerBase +{ + /// + /// Get version + /// + /// Version + [HttpGet] + public IActionResult Get() + { + var version = Assembly.GetEntryAssembly()?.GetCustomAttribute() + ?.InformationalVersion; + + return new ObjectResult(new {Version = version}) {StatusCode = 200}; + } } \ No newline at end of file diff --git a/src/Influunt.Host/Services/RedisCacheFallbackDecorator.cs b/src/Influunt.Host/Services/RedisCacheFallbackDecorator.cs index fa643e0..5a203bf 100644 --- a/src/Influunt.Host/Services/RedisCacheFallbackDecorator.cs +++ b/src/Influunt.Host/Services/RedisCacheFallbackDecorator.cs @@ -1,117 +1,116 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.Logging; -using StackExchange.Redis; - -namespace Influunt.Host.Services -{ - internal class RedisCacheFallbackDecorator : IDistributedCache - { - private readonly IDistributedCache _distributedCache; - private readonly ILogger _logger; - private readonly TimeSpan _redisPauseTime = TimeSpan.FromMinutes(1); - private DateTime _lastRedisFailTime = DateTime.MinValue; - - public RedisCacheFallbackDecorator(IDistributedCache distributedCache, - ILogger logger) - { - _distributedCache = distributedCache; - _logger = logger; - } - - public byte[] Get(string key) - { - return ExecuteCatched(() => _distributedCache.Get(key.ToApplicationKey())); - } - - public Task GetAsync(string key, CancellationToken token = new CancellationToken()) - { - return ExecuteCatchedAsync(() => _distributedCache.GetAsync(key.ToApplicationKey(), token)); - } - - public void Set(string key, byte[] value, DistributedCacheEntryOptions options) - { - ExecuteCatched(() => _distributedCache.Set(key.ToApplicationKey(), value, options)); - } - - public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, - CancellationToken token = new CancellationToken()) - { - return ExecuteCatchedAsync(() => _distributedCache.SetAsync(key.ToApplicationKey(), value, options, token)); - } - - public void Refresh(string key) - { - ExecuteCatched(() => _distributedCache.Refresh(key.ToApplicationKey())); - } - - public Task RefreshAsync(string key, CancellationToken token = new CancellationToken()) - { - return ExecuteCatchedAsync(() => _distributedCache.RefreshAsync(key.ToApplicationKey(), token)); - } - - public void Remove(string key) - { - ExecuteCatched(() => _distributedCache.Remove(key.ToApplicationKey())); - } - - public Task RemoveAsync(string key, CancellationToken token = new CancellationToken()) - { - return ExecuteCatchedAsync(() => _distributedCache.RemoveAsync(key.ToApplicationKey(), token)); - } - - private T ExecuteCatched(Func func) => ExecuteCatchedAsync(() => Task.Factory.StartNew(func)).Result; - - private async Task ExecuteCatchedAsync(Func> func) - { - if (IsRedisUnhealthy()) - return default; - - try - { - return await func(); - } - catch (Exception e) when (e is RedisConnectionException || e is RedisTimeoutException) - { - _lastRedisFailTime = DateTime.UtcNow; - _logger.LogError("Redis connection error. {Message}", e.Message); - return default; - } - } - - private void ExecuteCatched(Action action) => ExecuteCatchedAsync(() => Task.Factory.StartNew(action)).Wait(); - - private async Task ExecuteCatchedAsync(Func func) - { - if (IsRedisUnhealthy()) - return; - try - { - await func(); - } - catch (Exception e) when (e is RedisConnectionException || e is RedisTimeoutException) - { - _lastRedisFailTime = DateTime.UtcNow; - _logger.LogError("Redis connection error. {Message}", e.Message); - } - } - - private bool IsRedisUnhealthy() - { - return _lastRedisFailTime.Add(_redisPauseTime) > DateTime.UtcNow; - } - } - - internal static class KeyExtensions - { - private const string KeyPrefix = "Influunt."; - public static string ToApplicationKey(this string key) - { - return key.StartsWith(KeyPrefix) - ? key - : $"{KeyPrefix}{key}"; - } - } +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Logging; +using StackExchange.Redis; + +namespace Influunt.Host.Services; + +internal class RedisCacheFallbackDecorator : IDistributedCache +{ + private readonly IDistributedCache _distributedCache; + private readonly ILogger _logger; + private readonly TimeSpan _redisPauseTime = TimeSpan.FromMinutes(1); + private DateTime _lastRedisFailTime = DateTime.MinValue; + + public RedisCacheFallbackDecorator(IDistributedCache distributedCache, + ILogger logger) + { + _distributedCache = distributedCache; + _logger = logger; + } + + public byte[] Get(string key) + { + return ExecuteCatched(() => _distributedCache.Get(key.ToApplicationKey())); + } + + public Task GetAsync(string key, CancellationToken token = new CancellationToken()) + { + return ExecuteCatchedAsync(() => _distributedCache.GetAsync(key.ToApplicationKey(), token)); + } + + public void Set(string key, byte[] value, DistributedCacheEntryOptions options) + { + ExecuteCatched(() => _distributedCache.Set(key.ToApplicationKey(), value, options)); + } + + public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, + CancellationToken token = new CancellationToken()) + { + return ExecuteCatchedAsync(() => _distributedCache.SetAsync(key.ToApplicationKey(), value, options, token)); + } + + public void Refresh(string key) + { + ExecuteCatched(() => _distributedCache.Refresh(key.ToApplicationKey())); + } + + public Task RefreshAsync(string key, CancellationToken token = new CancellationToken()) + { + return ExecuteCatchedAsync(() => _distributedCache.RefreshAsync(key.ToApplicationKey(), token)); + } + + public void Remove(string key) + { + ExecuteCatched(() => _distributedCache.Remove(key.ToApplicationKey())); + } + + public Task RemoveAsync(string key, CancellationToken token = new CancellationToken()) + { + return ExecuteCatchedAsync(() => _distributedCache.RemoveAsync(key.ToApplicationKey(), token)); + } + + private T ExecuteCatched(Func func) => ExecuteCatchedAsync(() => Task.Factory.StartNew(func)).Result; + + private async Task ExecuteCatchedAsync(Func> func) + { + if (IsRedisUnhealthy()) + return default; + + try + { + return await func(); + } + catch (Exception e) when (e is RedisConnectionException || e is RedisTimeoutException) + { + _lastRedisFailTime = DateTime.UtcNow; + _logger.LogError("Redis connection error. {Message}", e.Message); + return default; + } + } + + private void ExecuteCatched(Action action) => ExecuteCatchedAsync(() => Task.Factory.StartNew(action)).Wait(); + + private async Task ExecuteCatchedAsync(Func func) + { + if (IsRedisUnhealthy()) + return; + try + { + await func(); + } + catch (Exception e) when (e is RedisConnectionException || e is RedisTimeoutException) + { + _lastRedisFailTime = DateTime.UtcNow; + _logger.LogError("Redis connection error. {Message}", e.Message); + } + } + + private bool IsRedisUnhealthy() + { + return _lastRedisFailTime.Add(_redisPauseTime) > DateTime.UtcNow; + } +} + +internal static class KeyExtensions +{ + private const string KeyPrefix = "Influunt."; + public static string ToApplicationKey(this string key) + { + return key.StartsWith(KeyPrefix) + ? key + : $"{KeyPrefix}{key}"; + } } \ No newline at end of file diff --git a/src/Influunt.Host/StartupModule.cs b/src/Influunt.Host/StartupModule.cs index 0e1aa32..95d56c1 100644 --- a/src/Influunt.Host/StartupModule.cs +++ b/src/Influunt.Host/StartupModule.cs @@ -15,7 +15,7 @@ namespace Influunt.Host; public class StartupModule : Module { - public override Type[] DependsModules => new[] {typeof(WebModule), typeof(StorageModule), typeof(RssModule)}; + public override Type[] DependsModules => [typeof(WebModule), typeof(StorageModule), typeof(RssModule)]; public override void Configure(IServiceCollection services) { diff --git a/src/Influunt.Host/ViewModels/FavoriteFeedItemViewModel.cs b/src/Influunt.Host/ViewModels/FavoriteFeedItemViewModel.cs index b62402a..1329cb1 100644 --- a/src/Influunt.Host/ViewModels/FavoriteFeedItemViewModel.cs +++ b/src/Influunt.Host/ViewModels/FavoriteFeedItemViewModel.cs @@ -1,39 +1,38 @@ using System; -namespace Influunt.Host.ViewModels +namespace Influunt.Host.ViewModels; + +/// +/// Favorite News feed item +/// +public class FavoriteFeedItemViewModel { /// - /// Favorite News feed item + /// News title + /// + public string Title { get; set; } + /// + /// Link to news + /// + public string Link { get; set; } + /// + /// News description + /// + public string Description { get; set; } + /// + /// Creation date + /// + public DateTime Date { get; set; } + /// + /// Channel name where news published + /// + public string ChannelName { get; set; } + /// + /// News Unique identifier in database + /// + public string Id { get; set; } + /// + /// News Unique identifier /// - public class FavoriteFeedItemViewModel - { - /// - /// News title - /// - public string Title { get; set; } - /// - /// Link to news - /// - public string Link { get; set; } - /// - /// News description - /// - public string Description { get; set; } - /// - /// Creation date - /// - public DateTime Date { get; set; } - /// - /// Channel name where news published - /// - public string ChannelName { get; set; } - /// - /// News Unique identifier in database - /// - public string Id { get; set; } - /// - /// News Unique identifier - /// - public string ItemHash {get; set;} - } + public string ItemHash {get; set;} } \ No newline at end of file diff --git a/src/Influunt.Host/ViewModels/FeedChannelViewModel.cs b/src/Influunt.Host/ViewModels/FeedChannelViewModel.cs index 275fef8..0bc8a1e 100644 --- a/src/Influunt.Host/ViewModels/FeedChannelViewModel.cs +++ b/src/Influunt.Host/ViewModels/FeedChannelViewModel.cs @@ -1,51 +1,49 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; - -namespace Influunt.Host.ViewModels -{ - /// - /// News channel - /// - public class FeedChannelViewModel : IValidatableObject - { - /// - /// Url for getting news - /// - public string Url { get; set; } - - /// - /// Channel name - /// - [Required] - public string Name { get; set; } - - /// - /// Show in infinity feed flag - /// - public bool Hidden { get; set; } = false; - - /// - /// Unique Identifier - /// - public string Id { get; set; } - - public IEnumerable Validate(ValidationContext validationContext) - { - var validationResults = new List(); - if (string.IsNullOrWhiteSpace(Name) - || string.IsNullOrWhiteSpace(Url)) - validationResults.Add(new ValidationResult("Can't be null or empty", - new[] {nameof(Name), nameof(Url)})); - - if (!Url.StartsWith("http://") - && !Url.StartsWith("https://")) - validationResults.Add(new ValidationResult("Should start with http or https", new[] {nameof(Url)})); - - if(!validationResults.Any()) - validationResults.Add(ValidationResult.Success); - - return validationResults; - } - } +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Influunt.Host.ViewModels; + +/// +/// News channel +/// +public class FeedChannelViewModel : IValidatableObject +{ + /// + /// Url for getting news + /// + public string Url { get; set; } + + /// + /// Channel name + /// + [Required] + public string Name { get; set; } + + /// + /// Show in infinity feed flag + /// + public bool Hidden { get; set; } = false; + + /// + /// Unique Identifier + /// + public string Id { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) + { + var validationResults = new List(); + if (string.IsNullOrWhiteSpace(Name) + || string.IsNullOrWhiteSpace(Url)) + validationResults.Add(new ValidationResult("Can't be null or empty", + new[] {nameof(Name), nameof(Url)})); + + if (!Url.StartsWith("http://") + && !Url.StartsWith("https://")) + validationResults.Add(new ValidationResult("Should start with http or https", new[] {nameof(Url)})); + + if(validationResults.Count == 0) + validationResults.Add(ValidationResult.Success); + + return validationResults; + } } \ No newline at end of file diff --git a/src/Influunt.Host/ViewModels/FeedItemViewModel.cs b/src/Influunt.Host/ViewModels/FeedItemViewModel.cs index 092c672..e59f4f7 100644 --- a/src/Influunt.Host/ViewModels/FeedItemViewModel.cs +++ b/src/Influunt.Host/ViewModels/FeedItemViewModel.cs @@ -1,35 +1,34 @@ using System; -namespace Influunt.Host.ViewModels +namespace Influunt.Host.ViewModels; + +/// +/// News feed item +/// +public class FeedItemViewModel { /// - /// News feed item + /// News title + /// + public string Title { get; set; } + /// + /// Link to news + /// + public string Link { get; set; } + /// + /// News description + /// + public string Description { get; set; } + /// + /// Creation date + /// + public DateTime Date { get; set; } + /// + /// Channel name where news published + /// + public string ChannelName { get; set; } + /// + /// News Unique identifier /// - public class FeedItemViewModel - { - /// - /// News title - /// - public string Title { get; set; } - /// - /// Link to news - /// - public string Link { get; set; } - /// - /// News description - /// - public string Description { get; set; } - /// - /// Creation date - /// - public DateTime Date { get; set; } - /// - /// Channel name where news published - /// - public string ChannelName { get; set; } - /// - /// News Unique identifier - /// - public string ItemHash {get; set;} - } + public string ItemHash {get; set;} } \ No newline at end of file diff --git a/src/Influunt.Host/ViewModels/MappingExtensions.cs b/src/Influunt.Host/ViewModels/MappingExtensions.cs index 1679e10..23f9067 100644 --- a/src/Influunt.Host/ViewModels/MappingExtensions.cs +++ b/src/Influunt.Host/ViewModels/MappingExtensions.cs @@ -2,117 +2,116 @@ using System.Linq; using Influunt.Feed.Entity; -namespace Influunt.Host.ViewModels +namespace Influunt.Host.ViewModels; + +/// +/// Extensions for class mapping +/// +public static class MappingExtensions { /// - /// Extensions for class mapping + /// User to UseViewModel mapping /// - public static class MappingExtensions + public static UserViewModel ToModel(this User user) { - /// - /// User to UseViewModel mapping - /// - public static UserViewModel ToModel(this User user) - { - if (user == null) - return null; + if (user is null) + return null; - return new UserViewModel - { - Email = user.Email, - Name = user.Name - }; - } + return new UserViewModel + { + Email = user.Email, + Name = user.Name + }; + } - /// - /// FeedItem to FeedItemViewModel mapping - /// - public static IEnumerable ToModel(this IEnumerable feedItem, IEnumerable channels) + /// + /// FeedItem to FeedItemViewModel mapping + /// + public static IEnumerable ToModel(this IEnumerable feedItem, IEnumerable channels) + { + return feedItem.Select(x => new FeedItemViewModel { - return feedItem.Select(x => new FeedItemViewModel - { - ChannelName = channels.FirstOrDefault(c => c.Id == x.ChannelId)?.Name, - Date = x.PubDate, - Description = x.Description, - Link = x.Link, - Title = x.Title, - ItemHash = x.Hash - }); - } + ChannelName = channels.FirstOrDefault(c => c.Id == x.ChannelId)?.Name, + Date = x.PubDate, + Description = x.Description, + Link = x.Link, + Title = x.Title, + ItemHash = x.Hash + }); + } - /// - /// FeedItemViewModel to FavoriteFeedItem mapping - /// - public static FavoriteFeedItem ToEntity(this FeedItemViewModel feedViewItem) + /// + /// FeedItemViewModel to FavoriteFeedItem mapping + /// + public static FavoriteFeedItem ToEntity(this FeedItemViewModel feedViewItem) + { + return new FavoriteFeedItem { - return new FavoriteFeedItem - { - PubDate = feedViewItem.Date, - Description = feedViewItem.Description, - Link = feedViewItem.Link, - Title = feedViewItem.Title, - Hash = feedViewItem.ItemHash, - ChannelName = feedViewItem.ChannelName - }; - } + PubDate = feedViewItem.Date, + Description = feedViewItem.Description, + Link = feedViewItem.Link, + Title = feedViewItem.Title, + Hash = feedViewItem.ItemHash, + ChannelName = feedViewItem.ChannelName + }; + } - /// - /// FavoriteFeedItem to FavoriteFeedItemViewModel mapping - /// - public static IEnumerable ToModel(this IEnumerable feedItem) + /// + /// FavoriteFeedItem to FavoriteFeedItemViewModel mapping + /// + public static IEnumerable ToModel(this IEnumerable feedItem) + { + return feedItem.Select(x => new FavoriteFeedItemViewModel { - return feedItem.Select(x => new FavoriteFeedItemViewModel - { - Date = x.PubDate, - Description = x.Description, - Link = x.Link, - Title = x.Title, - Id = x.Id, - ItemHash = x.Hash, - ChannelName = x.ChannelName - }); - } + Date = x.PubDate, + Description = x.Description, + Link = x.Link, + Title = x.Title, + Id = x.Id, + ItemHash = x.Hash, + ChannelName = x.ChannelName + }); + } - /// - /// FeedChannel to FeedChannelViewModel mapping - /// - public static IEnumerable ToModel(this IEnumerable channelItem) + /// + /// FeedChannel to FeedChannelViewModel mapping + /// + public static IEnumerable ToModel(this IEnumerable channelItem) + { + return channelItem.Select(x => new FeedChannelViewModel { - return channelItem.Select(x => new FeedChannelViewModel - { - Hidden = x.Hidden, - Id = x.Id, - Name = x.Name, - Url = x.Url - }); - } + Hidden = x.Hidden, + Id = x.Id, + Name = x.Name, + Url = x.Url + }); + } - /// - /// FeedChannel to FeedChannelViewModel mapping - /// - public static FeedChannelViewModel ToModel(this FeedChannel channelItem) + /// + /// FeedChannel to FeedChannelViewModel mapping + /// + public static FeedChannelViewModel ToModel(this FeedChannel channelItem) + { + return new FeedChannelViewModel { - return new FeedChannelViewModel - { - Hidden = channelItem.Hidden, - Id = channelItem.Id, - Name = channelItem.Name, - Url = channelItem.Url - }; - } + Hidden = channelItem.Hidden, + Id = channelItem.Id, + Name = channelItem.Name, + Url = channelItem.Url + }; + } - /// - /// FeedChannelViewModel to FeedChannel mapping - /// - public static FeedChannel ToEntity(this FeedChannelViewModel channelItem) + /// + /// FeedChannelViewModel to FeedChannel mapping + /// + public static FeedChannel ToEntity(this FeedChannelViewModel channelItem) + { + return new FeedChannel { - return new FeedChannel - { - Hidden = channelItem.Hidden, - Id = channelItem.Id, - Name = channelItem.Name, - Url = channelItem.Url - }; - } + Hidden = channelItem.Hidden, + Id = channelItem.Id, + Name = channelItem.Name, + Url = channelItem.Url + }; } } \ No newline at end of file diff --git a/src/Influunt.Host/ViewModels/UserViewModel.cs b/src/Influunt.Host/ViewModels/UserViewModel.cs index 9e8b4b0..741b187 100644 --- a/src/Influunt.Host/ViewModels/UserViewModel.cs +++ b/src/Influunt.Host/ViewModels/UserViewModel.cs @@ -1,17 +1,16 @@ -namespace Influunt.Host.ViewModels -{ - /// - /// User contract - /// - public class UserViewModel - { - /// - /// User email - /// - public string Email { get; set; } - /// - /// User name - /// - public string Name { get; set; } - } +namespace Influunt.Host.ViewModels; + +/// +/// User contract +/// +public class UserViewModel +{ + /// + /// User email + /// + public string Email { get; set; } + /// + /// User name + /// + public string Name { get; set; } } \ No newline at end of file diff --git a/src/Influunt.Host/WebModule.cs b/src/Influunt.Host/WebModule.cs index 6f278aa..1db7180 100644 --- a/src/Influunt.Host/WebModule.cs +++ b/src/Influunt.Host/WebModule.cs @@ -6,7 +6,6 @@ using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.HttpOverrides; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; using Skidbladnir.Modules; @@ -60,7 +59,7 @@ public override void Configure(IServiceCollection services) { Version = "v1", Title = "Influunt API", - Description = "Influunt (Rss agregator) Api" + Description = "Influunt (Rss aggregator) Api" }); c.CustomSchemaIds(type => type.FullName); var filePath = Path.Combine(AppContext.BaseDirectory, "Influunt.Host.xml"); diff --git a/src/Influunt.Storage/Entity/FavoriteFeedItemMap.cs b/src/Influunt.Storage/Entity/FavoriteFeedItemMap.cs index 0b47600..5e5151b 100644 --- a/src/Influunt.Storage/Entity/FavoriteFeedItemMap.cs +++ b/src/Influunt.Storage/Entity/FavoriteFeedItemMap.cs @@ -1,14 +1,12 @@ using Influunt.Feed.Entity; -using MongoDB.Bson; using Skidbladnir.Repository.MongoDB; -namespace Influunt.Storage.Entity +namespace Influunt.Storage.Entity; + +public class FavoriteFeedItemMap : EntityMapClass { - public class FavoriteFeedItemMap : EntityMapClass + public FavoriteFeedItemMap() { - public FavoriteFeedItemMap() - { - ToCollection("FavoriteFeedItem"); - } + ToCollection("FavoriteFeedItem"); } } \ No newline at end of file diff --git a/src/Influunt.Storage/Entity/FeedChannelMap.cs b/src/Influunt.Storage/Entity/FeedChannelMap.cs index dde3015..1e51aa1 100644 --- a/src/Influunt.Storage/Entity/FeedChannelMap.cs +++ b/src/Influunt.Storage/Entity/FeedChannelMap.cs @@ -1,15 +1,14 @@ -using Influunt.Feed.Entity; -using MongoDB.Bson; -using Skidbladnir.Repository.MongoDB; - -namespace Influunt.Storage.Entity -{ - public class FeedChannelMap : EntityMapClass - { - public FeedChannelMap() - { - ToCollection("FeedChannel"); - MapId(x => x.Id, BsonType.String); - } - } +using Influunt.Feed.Entity; +using MongoDB.Bson; +using Skidbladnir.Repository.MongoDB; + +namespace Influunt.Storage.Entity; + +public class FeedChannelMap : EntityMapClass +{ + public FeedChannelMap() + { + ToCollection("FeedChannel"); + MapId(x => x.Id, BsonType.String); + } } \ No newline at end of file diff --git a/src/Influunt.Storage/Entity/FeedItemMap.cs b/src/Influunt.Storage/Entity/FeedItemMap.cs index e54172a..f8e099d 100644 --- a/src/Influunt.Storage/Entity/FeedItemMap.cs +++ b/src/Influunt.Storage/Entity/FeedItemMap.cs @@ -2,14 +2,13 @@ using MongoDB.Bson; using Skidbladnir.Repository.MongoDB; -namespace Influunt.Storage.Entity +namespace Influunt.Storage.Entity; + +public class FeedItemMap : EntityMapClass { - public class FeedItemMap : EntityMapClass + public FeedItemMap() { - public FeedItemMap() - { - ToCollection("Feed"); - MapId(x => x.Id, BsonType.String); - } + ToCollection("Feed"); + MapId(x => x.Id, BsonType.String); } } \ No newline at end of file diff --git a/src/Influunt.Storage/Entity/UserMap.cs b/src/Influunt.Storage/Entity/UserMap.cs index 9e7d557..8dc5a1b 100644 --- a/src/Influunt.Storage/Entity/UserMap.cs +++ b/src/Influunt.Storage/Entity/UserMap.cs @@ -1,19 +1,18 @@ -using Influunt.Feed.Entity; -using MongoDB.Bson; -using Skidbladnir.Repository.MongoDB; -using System; - -namespace Influunt.Storage.Entity -{ - public class UserMap : EntityMapClass - { - public UserMap() - { - ToCollection("User"); - MapId(x => x.Id, BsonType.String); - MapField(f => f.LastActivity) - .SetDefaultValue(DateTime.UnixEpoch) - .SetIgnoreIfDefault(false); - } - } +using Influunt.Feed.Entity; +using MongoDB.Bson; +using Skidbladnir.Repository.MongoDB; +using System; + +namespace Influunt.Storage.Entity; + +public class UserMap : EntityMapClass +{ + public UserMap() + { + ToCollection("User"); + MapId(x => x.Id, BsonType.String); + MapField(f => f.LastActivity) + .SetDefaultValue(DateTime.UnixEpoch) + .SetIgnoreIfDefault(false); + } } \ No newline at end of file diff --git a/src/Influunt.Storage/Services/ChannelService.cs b/src/Influunt.Storage/Services/ChannelService.cs index 81c6910..5a4eb46 100644 --- a/src/Influunt.Storage/Services/ChannelService.cs +++ b/src/Influunt.Storage/Services/ChannelService.cs @@ -6,45 +6,44 @@ using System.Threading.Tasks; using Skidbladnir.Repository.Abstractions; -namespace Influunt.Storage.Services +namespace Influunt.Storage.Services; + +internal class ChannelService : IChannelService { - internal class ChannelService : IChannelService + private readonly IRepository _channelRepository; + + public ChannelService(IRepository channelRepository) + { + _channelRepository = channelRepository; + } + + public Task Get(string id) + { + return _channelRepository.GetAll().FirstOrDefaultAsync(x => x.Id == id); + } + + public async Task Add(FeedChannel channel) + { + await _channelRepository.Create(channel); + return channel; + } + + public Task Remove(User user, FeedChannel channel) + { + if (!channel.UserId.Equals(user.Id, StringComparison.OrdinalIgnoreCase)) + return Task.CompletedTask; + return _channelRepository.Delete(channel); + } + + public Task Update(User user, FeedChannel channel) + { + if (!channel.UserId.Equals(user.Id, StringComparison.OrdinalIgnoreCase)) + return Task.CompletedTask; + return _channelRepository.Update(channel); + } + + public async Task> GetUserChannels(User user) { - private readonly IRepository _channelRepository; - - public ChannelService(IRepository channelRepository) - { - _channelRepository = channelRepository; - } - - public Task Get(string id) - { - return _channelRepository.GetAll().FirstOrDefaultAsync(x => x.Id == id); - } - - public async Task Add(FeedChannel channel) - { - await _channelRepository.Create(channel); - return channel; - } - - public Task Remove(User user, FeedChannel channel) - { - if (!channel.UserId.Equals(user.Id, StringComparison.OrdinalIgnoreCase)) - return Task.CompletedTask; - return _channelRepository.Delete(channel); - } - - public Task Update(User user, FeedChannel channel) - { - if (!channel.UserId.Equals(user.Id, StringComparison.OrdinalIgnoreCase)) - return Task.CompletedTask; - return _channelRepository.Update(channel); - } - - public async Task> GetUserChannels(User user) - { - return await _channelRepository.GetAll().Where(c => c.UserId == user.Id).ToArrayAsync(); - } + return await _channelRepository.GetAll().Where(c => c.UserId == user.Id).ToArrayAsync(); } } \ No newline at end of file diff --git a/src/Influunt.Storage/Services/FavoriteService.cs b/src/Influunt.Storage/Services/FavoriteService.cs index 5225c7c..d05ac8b 100644 --- a/src/Influunt.Storage/Services/FavoriteService.cs +++ b/src/Influunt.Storage/Services/FavoriteService.cs @@ -6,56 +6,55 @@ using System.Threading.Tasks; using Skidbladnir.Repository.Abstractions; -namespace Influunt.Storage.Services +namespace Influunt.Storage.Services; + +internal class FavoriteService : IFavoriteFeedService { - internal class FavoriteService : IFavoriteFeedService + private readonly IRepository _favoriteRepository; + + public FavoriteService(IRepository favoriteRepository) { - private readonly IRepository _favoriteRepository; + _favoriteRepository = favoriteRepository; + } - public FavoriteService(IRepository favoriteRepository) + public async Task Add(User user, FeedItem favorite) + { + var favoriteItem = new FavoriteFeedItem { - _favoriteRepository = favoriteRepository; - } + PubDate = favorite.PubDate, + Description = favorite.Description, + Link = favorite.Link, + Title = favorite.Title, + UserId = user.Id, + ChannelId = favorite.ChannelId + }; + await _favoriteRepository.Create(favoriteItem); + return favoriteItem; + } - public async Task Add(User user, FeedItem favorite) - { - var favoriteItem = new FavoriteFeedItem - { - PubDate = favorite.PubDate, - Description = favorite.Description, - Link = favorite.Link, - Title = favorite.Title, - UserId = user.Id, - ChannelId = favorite.ChannelId - }; - await _favoriteRepository.Create(favoriteItem); - return favoriteItem; - } - - public async Task Remove(User user, string id) - { - var favorite = await _favoriteRepository.GetAll().FirstOrDefaultAsync(x=>x.Id == id); - if (favorite == null || !favorite.UserId.Equals(user.Id, StringComparison.OrdinalIgnoreCase)) - return; + public async Task Remove(User user, string id) + { + var favorite = await _favoriteRepository.GetAll().FirstOrDefaultAsync(x=>x.Id == id); + if (favorite is null || !favorite.UserId.Equals(user.Id, StringComparison.OrdinalIgnoreCase)) + return; - await _favoriteRepository.Delete(favorite); - } + await _favoriteRepository.Delete(favorite); + } - public async Task> GetUserFavorites(User user, int? offset) - { - return await GetFavorites(user, offset).ToArrayAsync(); - } + public async Task> GetUserFavorites(User user, int? offset) + { + return await GetFavorites(user, offset).ToArrayAsync(); + } - private IQueryable GetFavorites(User user, int? offset) - { - var query = _favoriteRepository.GetAll() - .Where(f => f.UserId == user.Id) - .OrderByDescending(f => f.PubDate); + private IQueryable GetFavorites(User user, int? offset) + { + var query = _favoriteRepository.GetAll() + .Where(f => f.UserId == user.Id) + .OrderByDescending(f => f.PubDate); - if (offset == null) - return query; + if (offset is null) + return query; - return query.Skip(offset.Value).Take(10); - } + return query.Skip(offset.Value).Take(10); } } \ No newline at end of file diff --git a/src/Influunt.Storage/Services/FeedService.cs b/src/Influunt.Storage/Services/FeedService.cs index ff0b924..d56c2c9 100644 --- a/src/Influunt.Storage/Services/FeedService.cs +++ b/src/Influunt.Storage/Services/FeedService.cs @@ -8,83 +8,83 @@ using Microsoft.Extensions.Logging; using Skidbladnir.Repository.Abstractions; -namespace Influunt.Storage.Services +namespace Influunt.Storage.Services; + +internal class FeedService : IFeedService { - internal class FeedService : IFeedService + private const int BatchSize = 100; + private readonly IChannelService _channelService; + private readonly IRepository _feedRepository; + private readonly ILogger _logger; + + public FeedService(IChannelService channelService, + IRepository feedRepository, + ILogger logger) { - private const int BatchSize = 100; - private readonly IChannelService _channelService; - private readonly IRepository _feedRepository; - private readonly ILogger _logger; + _channelService = channelService; + _feedRepository = feedRepository; + _logger = logger; + } - public FeedService(IChannelService channelService, - IRepository feedRepository, - ILogger logger) - { - _channelService = channelService; - _feedRepository = feedRepository; - _logger = logger; - } - public async Task> GetFeed(User user, int? offset = null, int count = 10) - { - var userChannels = await _channelService.GetUserChannels(user); - var visibleUserChannels = userChannels.Where(x => !x.Hidden).Select(x => x.Id).ToArray(); - if (!visibleUserChannels.Any()) - return Array.Empty(); + public async Task> GetFeed(User user, int? offset = null, int count = 10) + { + var userChannels = await _channelService.GetUserChannels(user); + var visibleUserChannels = userChannels.Where(x => !x.Hidden).Select(x => x.Id).ToArray(); + if (visibleUserChannels.Length == 0) + return Array.Empty(); - var feed = _feedRepository.GetAll().Where(x => x.UserId == user.Id && visibleUserChannels.Contains(x.ChannelId)) - .OrderByDescending(x => x.PubDate); - return await feed.GetChunkedFeed(offset, count).ToArrayAsync(); + var feed = _feedRepository.GetAll().Where(x => x.UserId == user.Id && visibleUserChannels.Contains(x.ChannelId)) + .OrderByDescending(x => x.PubDate); + return await feed.GetChunkedFeed(offset, count).ToArrayAsync(); - } + } - public async Task> GetFeed(User user, FeedChannel channel, int? offset = null, int count = 10) - { - if (!channel.UserId.Equals(user.Id, StringComparison.OrdinalIgnoreCase)) - return new List(); + public async Task> GetFeed(User user, FeedChannel channel, int? offset = null, int count = 10) + { + if (!channel.UserId.Equals(user.Id, StringComparison.OrdinalIgnoreCase)) + return new List(); - var feed = _feedRepository.GetAll().Where(x => x.UserId == user.Id && x.ChannelId == channel.Id) - .OrderByDescending(x => x.PubDate); + var feed = _feedRepository.GetAll().Where(x => x.UserId == user.Id && x.ChannelId == channel.Id) + .OrderByDescending(x => x.PubDate); - return await feed.GetChunkedFeed(offset, count).ToArrayAsync(); - } + return await feed.GetChunkedFeed(offset, count).ToArrayAsync(); + } - public async Task RemoveFeedByChannel(User user, FeedChannel channel) + public async Task RemoveFeedByChannel(User user, FeedChannel channel) + { + _logger.LogInformation($"Removing channel with id = {channel.Id} and name {channel.Name} from user {user.Id} with all feed items owned to channel"); + var page = 0; + var channelFeed = await _feedRepository.GetAll() + .Where(x => x.UserId == user.Id && x.ChannelId == channel.Id) + .GetChunkedFeed(page * BatchSize, BatchSize) + .ToArrayAsync(); + while (channelFeed.Length != 0) { - _logger.LogInformation($"Removing channel with id = {channel.Id} and name {channel.Name} from user {user.Id} with all feed items owned to channel"); - var page = 0; - var channelFeed = await _feedRepository.GetAll() - .Where( x=> x.UserId == user.Id && x.ChannelId == channel.Id) + foreach (var item in channelFeed) + await _feedRepository.Delete(item); + page++; + channelFeed = await _feedRepository.GetAll() + .Where(x => x.UserId == user.Id && x.ChannelId == channel.Id) .GetChunkedFeed(page * BatchSize, BatchSize) .ToArrayAsync(); - while(channelFeed.Any()) - { - foreach(var item in channelFeed) - await _feedRepository.Delete(item); - page++; - channelFeed = await _feedRepository.GetAll() - .Where( x=> x.UserId == user.Id && x.ChannelId == channel.Id) - .GetChunkedFeed(page * BatchSize, BatchSize) - .ToArrayAsync(); - } - _logger.LogInformation($"Removing channel with id = {channel.Id} and all channel feed completed"); } + _logger.LogInformation($"Removing channel with id = {channel.Id} and all channel feed completed"); + } - public async Task TryAddToFeed(User user, IEnumerable newFeedItems, FeedChannel channel = null) + public async Task TryAddToFeed(User user, IEnumerable newFeedItems, FeedChannel channel = null) + { + var addedCount = 0; + foreach (var item in newFeedItems) { - var addedCount = 0; - foreach (var item in newFeedItems) - { - item.UserId = user.Id; - if (channel != null) - item.ChannelId = channel.Id; - item.Hash = item.ComputeHash(); - if (await _feedRepository.GetAll().AnyAsync(x => x.UserId == user.Id && x.Hash == item.Hash)) - continue; - await _feedRepository.Create(item); - addedCount++; - } - return addedCount; + item.UserId = user.Id; + if (channel is not null) + item.ChannelId = channel.Id; + item.Hash = item.ComputeHash(); + if (await _feedRepository.GetAll().AnyAsync(x => x.UserId == user.Id && x.Hash == item.Hash)) + continue; + await _feedRepository.Create(item); + addedCount++; } + return addedCount; } } \ No newline at end of file diff --git a/src/Influunt.Storage/Services/HashMigrationWorkerModule.cs b/src/Influunt.Storage/Services/HashMigrationWorkerModule.cs index de8b627..955c052 100644 --- a/src/Influunt.Storage/Services/HashMigrationWorkerModule.cs +++ b/src/Influunt.Storage/Services/HashMigrationWorkerModule.cs @@ -10,37 +10,37 @@ using Skidbladnir.Modules; using Skidbladnir.Repository.Abstractions; -namespace Influunt.Storage.Services +namespace Influunt.Storage.Services; + +// Add missing hash and channel id for favorites +internal class HashMigrationWorkerModule : BackgroundModule { - // Add missing hash and channel id for favorites - internal class HashMigrationWorkerModule : BackgroundModule + public override async Task ExecuteAsync(IServiceProvider provider, CancellationToken cancellationToken = default) { - public override async Task ExecuteAsync(IServiceProvider provider, CancellationToken cancellationToken = default) + var logger = provider.GetRequiredService>(); + var favoriteRepository = provider.GetRequiredService>(); + var channelRepository = provider.GetRequiredService>(); + try { - var logger = provider.GetRequiredService>(); - var favoriteRepository = provider.GetRequiredService>(); - var channelRepository = provider.GetRequiredService>(); - try - { - var favoriteWithoutHash = await favoriteRepository.GetAll().Where(x => - string.IsNullOrEmpty(x.Hash) - || string.IsNullOrEmpty(x.ChannelId)).ToArrayAsync(); - logger.LogInformation($"Favorites count without hash = {favoriteWithoutHash.Length}"); - foreach (var item in favoriteWithoutHash) - { - if (!string.IsNullOrEmpty(item.ChannelId) && !string.IsNullOrEmpty(item.Hash)) - continue; - var channel = await channelRepository.GetAll().FirstOrDefaultAsync(x => x.UserId == item.UserId && x.Name == item.ChannelName); - item.ChannelId = channel?.Id; - item.Hash = item.ComputeHash(); - await favoriteRepository.Update(item); - } - logger.LogInformation("Fix hashes and channels completed"); - } - catch (Exception e) + var favoriteWithoutHash = await favoriteRepository.GetAll().Where(x => + string.IsNullOrEmpty(x.Hash) + || string.IsNullOrEmpty(x.ChannelId)) + .ToArrayAsync(); + logger.LogInformation($"Favorites count without hash = {favoriteWithoutHash.Length}"); + foreach (var item in favoriteWithoutHash) { - logger.LogError(e, "Error when compute missing favorites hashes"); + if (!string.IsNullOrEmpty(item.ChannelId) && !string.IsNullOrEmpty(item.Hash)) + continue; + var channel = await channelRepository.GetAll().FirstOrDefaultAsync(x => x.UserId == item.UserId && x.Name == item.ChannelName); + item.ChannelId = channel?.Id; + item.Hash = item.ComputeHash(); + await favoriteRepository.Update(item); } + logger.LogInformation("Fix hashes and channels completed"); + } + catch (Exception e) + { + logger.LogError(e, "Error when compute missing favorites hashes"); } } } \ No newline at end of file diff --git a/src/Influunt.Storage/Services/UserService.cs b/src/Influunt.Storage/Services/UserService.cs index 604e487..eb08059 100644 --- a/src/Influunt.Storage/Services/UserService.cs +++ b/src/Influunt.Storage/Services/UserService.cs @@ -6,66 +6,64 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace Influunt.Storage.Services -{ - internal class UserService : IUserService - { - private readonly IRepository _userRepository; - private readonly IHttpContextAccessor _httpContextAccessor; +namespace Influunt.Storage.Services; - public UserService(IRepository userRepository, IHttpContextAccessor httpContextAccessor) - { - _userRepository = userRepository; - _httpContextAccessor = httpContextAccessor; - } +internal class UserService : IUserService +{ + private readonly IRepository _userRepository; + private readonly IHttpContextAccessor _httpContextAccessor; + public UserService(IRepository userRepository, IHttpContextAccessor httpContextAccessor) + { + _userRepository = userRepository; + _httpContextAccessor = httpContextAccessor; + } - public async Task> GetUsers() - { - return await _userRepository.GetAll().ToArrayAsync(); - } + public async Task> GetUsers() + { + return await _userRepository.GetAll().ToArrayAsync(); + } - public async Task GetCurrentUser() - { - var contextUser = User.FromIdentity(_httpContextAccessor.HttpContext.User); - if (contextUser == null) - return null; + public async Task GetCurrentUser() + { + var contextUser = User.FromIdentity(_httpContextAccessor.HttpContext.User); + if (contextUser is null) + return null; - var user = await GetUserByEmail(contextUser.Email); + var user = await GetUserByEmail(contextUser.Email); - if (user == null) return await Add(contextUser); + if (user is null) return await Add(contextUser); - user.LastActivity = DateTime.UtcNow; - await Update(user); - return user; - } + user.LastActivity = DateTime.UtcNow; + await Update(user); + return user; + } - public Task GetUserById(string id) - { - return _userRepository.GetAll().FirstOrDefaultAsync(x => x.Id == id); - } + public Task GetUserById(string id) + { + return _userRepository.GetAll().FirstOrDefaultAsync(x => x.Id == id); + } - public async Task GetUserByEmail(string email) - { - var user = await _userRepository.GetAll().FirstOrDefaultAsync(u => u.Email.ToLower() == email.ToLower()); - return user; - } + public async Task GetUserByEmail(string email) + { + var user = await _userRepository.GetAll().FirstOrDefaultAsync(u => u.Email.Equals(email, StringComparison.OrdinalIgnoreCase)); + return user; + } - public async Task Add(User user) - { - user.LastActivity = DateTime.UtcNow; - await _userRepository.Create(user); - return user; - } + public async Task Add(User user) + { + user.LastActivity = DateTime.UtcNow; + await _userRepository.Create(user); + return user; + } - public Task Update(User updatedUser) - { - return _userRepository.Update(updatedUser); - } + public Task Update(User updatedUser) + { + return _userRepository.Update(updatedUser); + } - public Task Remove(User user) - { - return _userRepository.Delete(user); - } + public Task Remove(User user) + { + return _userRepository.Delete(user); } } \ No newline at end of file diff --git a/src/Influunt.Storage/StorageModule.cs b/src/Influunt.Storage/StorageModule.cs index 76c4879..f338b49 100644 --- a/src/Influunt.Storage/StorageModule.cs +++ b/src/Influunt.Storage/StorageModule.cs @@ -16,7 +16,7 @@ namespace Influunt.Storage; public class StorageModule : RunnableModule { - public override Type[] DependsModules => new []{ typeof(HashMigrationWorkerModule)}; + public override Type[] DependsModules => [typeof(HashMigrationWorkerModule)]; public override void Configure(IServiceCollection services) { // Register conventions @@ -66,7 +66,7 @@ public override async Task StartAsync(IServiceProvider provider, CancellationTok } } - private async Task CreateFeedIndexes(BaseMongoDbContext baseMongoContext) + private static async Task CreateFeedIndexes(BaseMongoDbContext baseMongoContext) { var collection = baseMongoContext.GetCollection(); var userIdAndChannelIdAndPubDateDefinition = Builders.IndexKeys @@ -87,7 +87,7 @@ private async Task CreateFeedIndexes(BaseMongoDbContext baseMongoContext) })); } - private async Task CreateFavoriteIndexes(BaseMongoDbContext baseMongoContext) + private static async Task CreateFavoriteIndexes(BaseMongoDbContext baseMongoContext) { var collection = baseMongoContext.GetCollection(); var userIdAndPubDateDefinition = Builders.IndexKeys @@ -99,7 +99,7 @@ private async Task CreateFavoriteIndexes(BaseMongoDbContext baseMongoContext) })); } - private async Task CreateChannelIndexes(BaseMongoDbContext baseMongoContext) + private static async Task CreateChannelIndexes(BaseMongoDbContext baseMongoContext) { var collection = baseMongoContext.GetCollection(); var userIdKeyDefinition = Builders.IndexKeys.Ascending(x => x.UserId); @@ -109,7 +109,7 @@ private async Task CreateChannelIndexes(BaseMongoDbContext baseMongoContext) })); } - private async Task CreateUserIndexes(BaseMongoDbContext baseMongoContext) + private static async Task CreateUserIndexes(BaseMongoDbContext baseMongoContext) { var collection = baseMongoContext.GetCollection(); var emailKeyDefinition = Builders.IndexKeys.Ascending(x => x.Email); From f417acfba9fa3a42a576f26dd58dfc1bd93ab8e3 Mon Sep 17 00:00:00 2001 From: Klabukov Erik Date: Sun, 10 Mar 2024 23:00:34 +0500 Subject: [PATCH 3/4] * Update tasks/launch json * Fix GetUserByEmail (rollback changes) * Update nodejs in dockerfile --- .dockerignore | 3 + .gitignore | 521 ++++++++++--------- .vscode/launch.json | 16 +- .vscode/tasks.json | 57 +- Dockerfile | 19 +- src/Influunt.Storage/Services/UserService.cs | 4 +- 6 files changed, 296 insertions(+), 324 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5d4d6c0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +node_modules +bin/ +obj/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6024550..b1c7959 100644 --- a/.gitignore +++ b/.gitignore @@ -1,260 +1,261 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# 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 -*.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 +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# 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 +*.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 +.env diff --git a/.vscode/launch.json b/.vscode/launch.json index 859b02f..d88866c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,19 +2,14 @@ "version": "0.2.0", "configurations": [ { - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md - "name": ".NET Core Launch (web)", + "name": "Influunt.Host", "type": "coreclr", "request": "launch", - "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. + "preLaunchTask": "build-Influunt.Host", "program": "${workspaceFolder}/src/Influunt.Host/bin/Debug/net8/Influunt.Host.dll", "args": [], "cwd": "${workspaceFolder}/src/Influunt.Host", "stopAtEntry": false, - // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser "serverReadyAction": { "action": "openExternally", "pattern": "\\bNow listening on:\\s+(https?://\\S+)" @@ -23,13 +18,8 @@ "ASPNETCORE_ENVIRONMENT": "Development" }, "sourceFileMap": { - "/Views": "${workspaceFolder}/Views" + "/Views": "${workspaceFolder}/src/Influunt.Host/Views" } - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach" } ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 539877c..3b1c986 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,42 +1,17 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "build", - "command": "dotnet", - "type": "process", - "args": [ - "build", - "${workspaceFolder}/src/Influunt.Host/Influunt.Host.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "publish", - "command": "dotnet", - "type": "process", - "args": [ - "publish", - "${workspaceFolder}/src/Influunt.Host/Influunt.Host.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "watch", - "command": "dotnet", - "type": "process", - "args": [ - "watch", - "run", - "${workspaceFolder}/src/Influunt.Host/Influunt.Host.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - } - ] +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build-Influunt.Host", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/src/Influunt.Host/Influunt.Host.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] } \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 34c6e6b..878a205 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,16 +8,16 @@ FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build COPY . /build COPY --from=version /version /build/version WORKDIR /build -RUN apt-get update -yq ;\ - apt-get install curl gnupg -yq ;\ - curl -sL https://deb.nodesource.com/setup_14.x | bash - ;\ +RUN apt-get update -yq &&\ + apt-get install curl gnupg -yq &&\ + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - &&\ apt-get install -y nodejs - -RUN sed -i -e "s/0-develop<\/Version>/$(cat version | cut -c2- )<\/Version>/g" src/Influunt.Host/Influunt.Host.csproj;\ - dotnet restore -s https://api.nuget.org/v3/index.json; \ - dotnet build --no-restore -c Release; \ - dotnet publish ./src/Influunt.Host/Influunt.Host.csproj -c Release -o /app --no-build; \ - dotnet nuget locals http-cache --clear;\ +ENV NODE_OPTIONS=--openssl-legacy-provider +RUN sed -i -e "s/0-develop<\/Version>/$(cat version | cut -c2- )<\/Version>/g" src/Influunt.Host/Influunt.Host.csproj &&\ + dotnet restore -s https://api.nuget.org/v3/index.json &&\ + dotnet build --no-restore -c Release &&\ + dotnet publish ./src/Influunt.Host/Influunt.Host.csproj -c Release -o /app --no-build &&\ + dotnet nuget locals http-cache --clear &&\ dotnet nuget locals temp --clear @@ -26,4 +26,5 @@ FROM mcr.microsoft.com/dotnet/aspnet:8.0-bookworm-slim as influunt COPY --from=build /app /influunt WORKDIR /influunt EXPOSE 80 +ENV ASPNETCORE_HTTP_PORTS=80 ENTRYPOINT ["dotnet", "Influunt.Host.dll"] diff --git a/src/Influunt.Storage/Services/UserService.cs b/src/Influunt.Storage/Services/UserService.cs index eb08059..3b04898 100644 --- a/src/Influunt.Storage/Services/UserService.cs +++ b/src/Influunt.Storage/Services/UserService.cs @@ -46,7 +46,9 @@ public Task GetUserById(string id) public async Task GetUserByEmail(string email) { - var user = await _userRepository.GetAll().FirstOrDefaultAsync(u => u.Email.Equals(email, StringComparison.OrdinalIgnoreCase)); + var user = await _userRepository + .GetAll() + .FirstOrDefaultAsync(u => u.Email.ToLower() == email.ToLower()); return user; } From 0d3880b957104ff741147730ec1fdc3691b64fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=BB=D0=B0=D0=B1=D1=83=D0=BA=D0=BE=D0=B2=20=D0=AD?= =?UTF-8?q?=D1=80=D0=B8=D0=BA=20=D0=9A=D0=BE=D0=BD=D1=81=D1=82=D0=B0=D0=BD?= =?UTF-8?q?=D1=82=D0=B8=D0=BD=D0=BE=D0=B2=D0=B8=D1=87?= Date: Tue, 3 Jun 2025 22:57:40 +0500 Subject: [PATCH 4/4] Disable commit hash to version --- Dockerfile | 2 +- .../Influunt.Feed.Rss.csproj | 1 + src/Influunt.Feed/Influunt.Feed.csproj | 1 + src/Influunt.Host/ClientApp/package.json | 68 +++++++++---------- src/Influunt.Host/Influunt.Host.csproj | 1 + src/Influunt.Storage/Influunt.Storage.csproj | 1 + 6 files changed, 39 insertions(+), 35 deletions(-) diff --git a/Dockerfile b/Dockerfile index 878a205..515a154 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,5 +26,5 @@ FROM mcr.microsoft.com/dotnet/aspnet:8.0-bookworm-slim as influunt COPY --from=build /app /influunt WORKDIR /influunt EXPOSE 80 -ENV ASPNETCORE_HTTP_PORTS=80 +ENV ASPNETCORE_URLS=http://*:80 ENTRYPOINT ["dotnet", "Influunt.Host.dll"] diff --git a/src/Influunt.Feed.Rss/Influunt.Feed.Rss.csproj b/src/Influunt.Feed.Rss/Influunt.Feed.Rss.csproj index 81736b3..f4c5ec1 100644 --- a/src/Influunt.Feed.Rss/Influunt.Feed.Rss.csproj +++ b/src/Influunt.Feed.Rss/Influunt.Feed.Rss.csproj @@ -6,6 +6,7 @@ Influunt Influunt - Simple Rss Agregator Klabukov Erik + false diff --git a/src/Influunt.Feed/Influunt.Feed.csproj b/src/Influunt.Feed/Influunt.Feed.csproj index c419f55..9d4e64d 100644 --- a/src/Influunt.Feed/Influunt.Feed.csproj +++ b/src/Influunt.Feed/Influunt.Feed.csproj @@ -6,6 +6,7 @@ Influunt Influunt - Simple Rss Agregator Klabukov Erik + false diff --git a/src/Influunt.Host/ClientApp/package.json b/src/Influunt.Host/ClientApp/package.json index f6a5cc5..29b6559 100644 --- a/src/Influunt.Host/ClientApp/package.json +++ b/src/Influunt.Host/ClientApp/package.json @@ -1,34 +1,34 @@ -{ - "name": "influunt", - "version": "0.1.0", - "private": true, - "scripts": { - "serve": "vue-cli-service serve", - "build": "vue-cli-service build", - "lint": "vue-cli-service lint" - }, - "dependencies": { - "@fortawesome/fontawesome-svg-core": "^1.2.32", - "@fortawesome/free-brands-svg-icons": "^5.15.1", - "@fortawesome/free-solid-svg-icons": "^5.15.1", - "@fortawesome/vue-fontawesome": "^2.0.0", - "bootstrap": "^4.5.2", - "bootstrap-vue": "^2.16.0", - "core-js": "^2.6.5", - "register-service-worker": "^1.6.2", - "vue": "^2.6.12", - "vue-router": "^3.0.3", - "vue2-touch-events": "^2.3.2", - "vuex": "^3.5.1" - }, - "devDependencies": { - "@vue/cli-plugin-babel": "^3.0.5", - "@vue/cli-plugin-eslint": "^3.0.5", - "@vue/cli-plugin-pwa": "^3.0.5", - "@vue/cli-service": "^3.0.5", - "babel-eslint": "^10.0.1", - "eslint": "^5.16.0", - "eslint-plugin-vue": "^5.0.0", - "vue-template-compiler": "^2.6.10" - } -} +{ + "name": "influunt", + "version": "0.1.0", + "private": true, + "scripts": { + "serve": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve", + "build": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build", + "lint": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service lint" + }, + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^1.2.32", + "@fortawesome/free-brands-svg-icons": "^5.15.1", + "@fortawesome/free-solid-svg-icons": "^5.15.1", + "@fortawesome/vue-fontawesome": "^2.0.0", + "bootstrap": "^4.5.2", + "bootstrap-vue": "^2.16.0", + "core-js": "^2.6.5", + "register-service-worker": "^1.6.2", + "vue": "^2.6.12", + "vue-router": "^3.0.3", + "vue2-touch-events": "^2.3.2", + "vuex": "^3.5.1" + }, + "devDependencies": { + "@vue/cli-plugin-babel": "^3.0.5", + "@vue/cli-plugin-eslint": "^3.0.5", + "@vue/cli-plugin-pwa": "^3.0.5", + "@vue/cli-service": "^3.0.5", + "babel-eslint": "^10.0.1", + "eslint": "^5.16.0", + "eslint-plugin-vue": "^5.0.0", + "vue-template-compiler": "^2.6.10" + } +} diff --git a/src/Influunt.Host/Influunt.Host.csproj b/src/Influunt.Host/Influunt.Host.csproj index 7f04cef..c04b05d 100644 --- a/src/Influunt.Host/Influunt.Host.csproj +++ b/src/Influunt.Host/Influunt.Host.csproj @@ -11,6 +11,7 @@ Klabukov Erik 0-develop true + false diff --git a/src/Influunt.Storage/Influunt.Storage.csproj b/src/Influunt.Storage/Influunt.Storage.csproj index ed239eb..848bbed 100644 --- a/src/Influunt.Storage/Influunt.Storage.csproj +++ b/src/Influunt.Storage/Influunt.Storage.csproj @@ -6,6 +6,7 @@ Influunt Influunt - Simple Rss Agregator Klabukov Erik + false