Simplified UI & Started database backend implementations

This commit is contained in:
Max Holleman 2023-05-19 14:40:57 +02:00
parent 24d62d79dc
commit 43cb208910
9 changed files with 117 additions and 38 deletions

View File

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SharpRss.Models
{
public class FeedItemModel
{
/// <summary>
/// Last time the item is fetched.
/// </summary>
public DateTime LastUpdated { get; set; }
/// <summary>
/// The feed in which the item is part.
/// </summary>
public string FeedId { get; set; }
/// <summary>
/// If the item is read.
/// </summary>
public string Read { get; set; }
public string Type { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Link { get; set; }
public DateTime? PublishingDate { get; set; }
public string Author { get; set; }
public string Id { get; set; }
public string[] Categories { get; set; }
public string Content { get; set; }
}
}

View File

@ -16,11 +16,17 @@ namespace SharpRss.Models
FeedId = Guid.NewGuid().ToString(); FeedId = Guid.NewGuid().ToString();
FeedUrl = rssFeedUrl; FeedUrl = rssFeedUrl;
} }
public string FeedUrl { get; set; }
public string FeedId { get; private set; } public string FeedId { get; private set; }
public string CategoryId { get; set; } = ""; public string CategoryId { get; set; } = "";
public string FeedType { get; set; }
public string FeedUrl { get; set; }
public string Description { get; set; }
public string Language { get; set; }
public string Copyright { get; set; }
public DateTime LastUpdated { get; set; }
public string ImageUrl { get; set; }
public static FeedModel Create(string url, string feedId, string categoryId) public static FeedModel Create(string url, string feedId, string categoryId)
{ {
FeedModel feedModel = new FeedModel() FeedModel feedModel = new FeedModel()

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dapper; using Dapper;
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
@ -78,22 +79,23 @@ namespace SharpRss.Services
private async void InitializeDb() private async void InitializeDb()
{ {
Log.Verbose("Checking database..."); Log.Verbose("Checking database...");
HashSet<string> failed = new HashSet<string>();
_sqlConn.Open(); _sqlConn.Open();
// Check category_data table
var queryResponse = await _sqlConn.QueryAsync("CREATE TABLE IF NOT EXISTS category_data (name STRING NOT NULL, hex_color STRING NOT NULL, path_icon STRING, category_id STRING PRIMARY KEY, CONSTRAINT name UNIQUE (name))"); var queryResponse = await _sqlConn.QueryAsync("CREATE TABLE IF NOT EXISTS category_data (name STRING NOT NULL, hex_color STRING NOT NULL, path_icon STRING, category_id STRING PRIMARY KEY, CONSTRAINT name UNIQUE (name))");
if (queryResponse.Any()) if (queryResponse.Any()) failed.Add("category_data");
{
_sqlConn.Close(); // Check feed_data table
_sqlConn.Dispose();
throw new SqliteException("Error initializing database!", 0);
}
queryResponse = await _sqlConn.QueryAsync("CREATE TABLE IF NOT EXISTS feed_data (url STRING NOT NULL, feed_id STRING PRIMARY KEY, category_id STRING NOT NULL DEFAULT '', CONSTRAINT url, UNIQUE (url))"); queryResponse = await _sqlConn.QueryAsync("CREATE TABLE IF NOT EXISTS feed_data (url STRING NOT NULL, feed_id STRING PRIMARY KEY, category_id STRING NOT NULL DEFAULT '', CONSTRAINT url, UNIQUE (url))");
if (queryResponse.Any()) if (queryResponse.Any()) failed.Add("feed_data");
_sqlConn.Close();
if (failed.Any())
{ {
_sqlConn.Close(); var joined = string.Join(',', failed);
_sqlConn.Dispose(); Log.Error("Failed to initialize table(s): {TableNames}", joined);
throw new SqliteException("Error initializing database!", 0);
} }
_sqlConn.Close(); else
Log.Verbose("Checking database done!"); Log.Verbose("Checking database done!");
} }

View File

@ -10,7 +10,7 @@ namespace SharpRss.Services
/// <summary> /// <summary>
/// Managing RSS feeds and categories. /// Managing RSS feeds and categories.
/// </summary> /// </summary>
public class RssService public class RssService : IDisposable
{ {
public RssService() public RssService()
{ {
@ -85,5 +85,10 @@ namespace SharpRss.Services
FeedModel feedModel = new FeedModel(rssUrl, category); FeedModel feedModel = new FeedModel(rssUrl, category);
await _dbService.AddFeedsAsync(new HashSet<FeedModel>() { feedModel }); await _dbService.AddFeedsAsync(new HashSet<FeedModel>() { feedModel });
} }
public void Dispose()
{
_dbService.Dispose();
}
} }
} }

View File

@ -2,6 +2,7 @@ using System;
using System.IO; using System.IO;
using Serilog; using Serilog;
using Serilog.Formatting.Json; using Serilog.Formatting.Json;
using Serilog.Sinks.SystemConsole.Themes;
using ToolQit; using ToolQit;
using ToolQit.Containers; using ToolQit.Containers;

View File

@ -0,0 +1,19 @@
using CodeHollow.FeedReader;
using SharpRss.Models;
namespace WebSharpRSS.Models
{
public class FeedItemData
{
public FeedItemData(FeedItem feedItem)
{
FeedItem = feedItem;
}
public FeedModel FeedModel { get; set; }
public FeedItem FeedItem { get; set; }
public string? Icon {get; set; }
public string? FaviconUrl { get; set; }
public string? CategoryColorHex { get; set; }
public bool Read { get; set; }
}
}

View File

@ -23,6 +23,7 @@ namespace WebSharpRSS.Models
{ {
_service = rssService; _service = rssService;
FeedModel = feedModel; FeedModel = feedModel;
Initialize(); Initialize();
} }

View File

@ -4,27 +4,34 @@
@using WebSharpRSS.Models; @using WebSharpRSS.Models;
@using SharpRss.Services @using SharpRss.Services
@*@inject RssService _rssService;*@
@inject FeedStateContainer _stateContainer; @inject FeedStateContainer _stateContainer;
<MudGrid Spacing="3" Justify="Justify.FlexStart"> <MudStack Spacing="2" Class="ml-2 mr-2">
@foreach (var feedItem in _items) @foreach (var feedItemData in _itemDatas)
{ {
<MudItem xs="6"> <MudItem>
<MudCard> <MudCard>
<MudCardContent> <MudCardContent>
<MudText>@feedItem.Title</MudText> <div style="justify-self: start;" class="d-flex align-center">
<MudText Typo="Typo.body2">@feedItem.Description</MudText> @if (feedItemData.Icon != null)
<MudText Typo="Typo.overline">@feedItem.PublishingDate.ToString()</MudText> {
<MudIcon Icon="@feedItemData.Icon" Style="@($"color:{feedItemData.CategoryColorHex}")" />
}
@if (feedItemData.FaviconUrl != null)
{
<MudImage Src="@feedItemData.FaviconUrl" ObjectFit="ObjectFit.Contain" />
}
<MudText Class="d-inline pa-2 align-center">@feedItemData.FeedItem.Title</MudText>
</div>
<MudText Typo="Typo.body2">@feedItemData.FeedItem.Description</MudText>
<MudText Typo="Typo.overline">@feedItemData.FeedItem.PublishingDate.ToString()</MudText>
</MudCardContent> </MudCardContent>
</MudCard> </MudCard>
</MudItem> </MudItem>
} }
</MudGrid> </MudStack>
@code { @code {
private HashSet<FeedItem> _items = new HashSet<FeedItem>();
protected override void OnInitialized() protected override void OnInitialized()
{ {
UpdateFeeds(); UpdateFeeds();
@ -35,19 +42,25 @@
UpdateFeeds(); UpdateFeeds();
InvokeAsync(StateHasChanged); InvokeAsync(StateHasChanged);
} }
private HashSet<FeedItemData> _itemDatas = new HashSet<FeedItemData>();
private TreeItemData? _treeItemData;
private void UpdateFeeds() private void UpdateFeeds()
{ {
if (_stateContainer.TreeItem == null) return; if (_stateContainer.TreeItem == null) return;
if (_stateContainer.TreeItem.Feed != null) _treeItemData = _stateContainer.TreeItem;
_items = _stateContainer.TreeItem.Feed.Items.ToHashSet(); if (_treeItemData.Feed != null)
if (_stateContainer.TreeItem.Feeds != null)
{ {
_items = new HashSet<FeedItem>(); Feed feed = _treeItemData.Feed;
foreach (var itemData in _stateContainer.TreeItem.Feeds) _itemDatas = feed.Items.Select(x => new FeedItemData(x) { Icon = _treeItemData.Icon, FaviconUrl = _treeItemData.FaviconUrl, CategoryColorHex = _treeItemData.CategoryModel?.HexColor }).ToHashSet();
}
else if (_treeItemData.Feeds != null)
{ {
if (itemData.Feed == null) continue; HashSet<FeedItemData> items = new HashSet<FeedItemData>();
_items.UnionWith(itemData.Feed.Items); foreach (var treeItem in _treeItemData.Feeds)
{
if (treeItem.Feed == null) continue;
items.UnionWith(treeItem.Feed.Items.Select(x => new FeedItemData(x) { Icon = treeItem.Icon, FaviconUrl = treeItem.FaviconUrl, CategoryColorHex = _treeItemData.CategoryModel?.HexColor }));
_itemDatas = items.OrderBy(x => x.FeedItem.PublishingDate).Reverse().ToHashSet();
} }
} }
} }

View File

@ -63,6 +63,6 @@
_guideItems.UnionWith(items.Select(x => x is CategoryModel model ? new TreeItemData(model, _rssService) : x is FeedModel feedModel ? new TreeItemData(feedModel, _rssService) : throw new ArgumentException("Arg x is invalid!"))); _guideItems.UnionWith(items.Select(x => x is CategoryModel model ? new TreeItemData(model, _rssService) : x is FeedModel feedModel ? new TreeItemData(feedModel, _rssService) : throw new ArgumentException("Arg x is invalid!")));
StateHasChanged(); StateHasChanged();
Log.Verbose(" Guide initialized!"); Log.Verbose("Guide initialized!");
} }
} }