Implementing database backend.

This commit is contained in:
Max 2023-05-21 21:56:37 +02:00
parent 6d2fce5290
commit 367949c200
7 changed files with 141 additions and 136 deletions

View File

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

View File

@ -4,16 +4,20 @@ namespace SharpRss.Models
{ {
public class FeedModel public class FeedModel
{ {
public string FeedId { get; set; } public FeedModel(string rssUrl)
public string GroupId { get; set; } {
public string FeedType { get; set; } Id = Guid.NewGuid().ToString();
public string FeedUrl { get; set; } Url = rssUrl;
public string Description { get; set; } }
public string Language { get; set; } public string Id { get; set; }
public string Copyright { get; set; } public string Url { get; set; }
public DateTime LastUpdated { get; set; } public string? GroupId { get; set; }
public string ImageUrl { get; set; } public string FeedType { get; set; } = string.Empty;
public int TotalItems { get; set; } public string Description { get; set; } = string.Empty;
public int TotalRead { get; set; } public string Language { get; set; } = string.Empty;
public string Copyright { get; set; } = string.Empty;
public DateTimeOffset DateAdded { get; set; }
public DateTimeOffset LastUpdated { get; set; }
public string ImageUrl { get; set; } = string.Empty;
} }
} }

View File

@ -5,15 +5,15 @@ namespace SharpRss.Models
{ {
public class GroupModel public class GroupModel
{ {
public GroupModel(string name) public GroupModel()
{ {
Name = name;
HexColor = Utilities.GenerateRandomHexColor(); HexColor = Utilities.GenerateRandomHexColor();
Id = Guid.NewGuid().ToString(); Id = Guid.NewGuid().ToString();
} }
public string Name { get; set; }
public string Name { get; set; } = string.Empty;
public string HexColor { get; set; } public string HexColor { get; set; }
public string Icon { get; set; } public string Icon { get; set; } = string.Empty;
public string Id { get; private set; } public string Id { get; set; }
} }
} }

View File

@ -23,86 +23,139 @@ namespace SharpRss.Services
private readonly string _feedTable = "feed_data"; private readonly string _feedTable = "feed_data";
private readonly string _feedItemTable = "feed_item_data"; private readonly string _feedItemTable = "feed_item_data";
public async Task RemoveGroupFromFeedsAsync(string groupId) // Group
public async Task<HashSet<GroupModel?>> GetGroupsAsync(string? groupName = null)
{ {
await _sqlConn.QueryAsync("UPDATE feed_data SET group_id=NULL WHERE group_id=@GroupId", new { GroupId = groupId });
}
public async Task<bool> AddCategoriesAsync(HashSet<GroupModel> categories)
{
bool result = true;
_sqlConn.Open(); _sqlConn.Open();
foreach (var categoryModel in categories) SqliteCommand cmd = new SqliteCommand(groupName != null ? $"SELECT * FROM {_groupTable} WHERE name=@name;" : $"SELECT * FROM {_groupTable}", _sqlConn)
{ {
await _sqlConn.QueryAsync("INSERT INTO category_data (name, hex_color, path_icon, category_id) VALUES(@catName, @hexColor, @pathIcon, @categoryId) ON CONFLICT(name) DO UPDATE SET hex_color=@hexColor, path_icon=@pathIcon", Parameters =
new { catName = categoryModel.Name, hexColor = categoryModel.HexColor, pathIcon = categoryModel.Icon, categoryId = categoryModel.Id });
}
_sqlConn.Close();
return result;
}
public async Task<bool> AddFeedsAsync(HashSet<FeedModel> feeds)
{ {
bool result = true; new SqliteParameter("name", groupName)
_sqlConn.Open();
foreach (var feedModel in feeds)
{
await _sqlConn.QueryAsync("INSERT OR REPLACE INTO feed_data(url, feed_id, category_id) VALUES(@url, @feedId, @categoryId) ON CONFLICT(url) DO UPDATE SET category_id=@categoryId", new { url = feedModel.FeedUrl, feedId = feedModel.FeedId, categoryId = feedModel.GroupId });
} }
_sqlConn.Close(); };
return result;
}
public async Task<HashSet<GroupModel>> GetCategoriesAsync()
{
HashSet<GroupModel> categories = new HashSet<GroupModel>();
_sqlConn.Open();
SqliteCommand cmd = _sqlConn.CreateCommand();
cmd.CommandText = "SELECT * FROM category_data";
await using SqliteDataReader reader = await cmd.ExecuteReaderAsync(); await using SqliteDataReader reader = await cmd.ExecuteReaderAsync();
HashSet<GroupModel?> groups = new HashSet<GroupModel?>();
while (reader.Read()) while (reader.Read())
{ {
//categories.Add(GroupModel.Create(reader["name"].ToString(), reader["hex_color"].ToString(), reader["category_id"].ToString())); groups.Add(new GroupModel()
{
Name = reader["name"].ToString(),
HexColor = reader["hex_color"].ToString(),
Icon = reader["icon"].ToString(),
Id = reader["id"].ToString()
});
} }
_sqlConn.Close(); _sqlConn.Close();
return categories; return groups;
} }
public async Task<HashSet<FeedModel>> GetFeedsAsync(string? categoryId = null) /// <summary>
/// Creates a group if not exists else will update the group.
/// </summary>
/// <param name="groupModel"></param>
/// <returns></returns>
public async Task<bool> SetGroupAsync(GroupModel groupModel)
{
bool result = false;
_sqlConn.Open();
SqliteCommand cmd = new SqliteCommand($"INSERT OR REPLACE INTO {_groupTable} (id, hex_color, icon, name) VALUES (IFNULL((SELECT id FROM {_groupTable} WHERE name=@name), @id), @hexColor, @icon, @name)", _sqlConn)
{
Parameters =
{
new SqliteParameter("id", groupModel.Id),
new SqliteParameter("hexColor", groupModel.HexColor),
new SqliteParameter("icon", groupModel.Icon),
new SqliteParameter("name", groupModel.Name)
}
};
int affected = await cmd.ExecuteNonQueryAsync();
if (affected != 0)
result = true;
_sqlConn.Close();
return result;
}
public async Task<bool> RemoveGroupAsync(GroupModel groupModel)
{
bool result = false;
_sqlConn.Open();
// Remove the group and remove the feeds that were part of the group.
SqliteCommand cmd = new SqliteCommand($"DELETE FROM {_groupTable} WHERE id=@id; UPDATE {_feedTable} SET group_id=NULL WHERE group_id=@id", _sqlConn)
{
Parameters =
{
new SqliteParameter("id", groupModel.Id)
}
};
int affected = await cmd.ExecuteNonQueryAsync();
if (affected != 0)
result = true;
_sqlConn.Close();
return result;
}
// Feed
public async Task<HashSet<FeedModel>> GetFeedsAsync(string? feedName = null)
{ {
HashSet<FeedModel> feeds = new HashSet<FeedModel>(); HashSet<FeedModel> feeds = new HashSet<FeedModel>();
_sqlConn.Open(); _sqlConn.Open();
SqliteCommand cmd = new SqliteCommand(feedName != null ? $"SELECT * FROM {_feedTable} WHERE name=@name" : $"SELECT * FROM {_feedTable}", _sqlConn)
SqliteCommand cmd = _sqlConn.CreateCommand();
cmd.CommandText = categoryId == null ? "SELECT * FROM feed_data" : "SELECT * FROM feed_data WHERE category_id=@categoryId";
if (categoryId != null)
cmd.Parameters.Add(new SqliteParameter("categoryId", categoryId));
await using SqliteDataReader reader = await cmd.ExecuteReaderAsync();
while (reader.Read())
{ {
//feeds.Add(FeedModel.Create(reader["url"].ToString(), reader["feed_id"].ToString(), reader["category_id"].ToString())); Parameters =
{
new SqliteParameter("name", feedName)
} }
};
int affected = await cmd.ExecuteNonQueryAsync();
Log.Verbose("{FeedAmount} feeds found!", affected);
_sqlConn.Close(); _sqlConn.Close();
return feeds; return feeds;
} }
public async Task<bool> AddFeedAsync(FeedModel feedModel)
{
bool result = false;
_sqlConn.Open();
SqliteCommand cmd = new SqliteCommand($"INSERT OR REPLACE INTO {_feedTable} (id, url, group_id, feed_type, description, language, copyright, date_added, last_updated, image_url)" +
$"VALUES (IFNULL((SELECT id FROM {_feedTable} WHERE url=@url), @id), @url, @groupId, @feedType, @description, @language, @copyright, @dateAdded, @lastUpdated, @imageUrl)", _sqlConn)
{
Parameters =
{
new SqliteParameter("id", feedModel.Id),
new SqliteParameter("url", feedModel.Url),
new SqliteParameter("groupId", feedModel.GroupId),
new SqliteParameter("feedType", feedModel.FeedType),
new SqliteParameter("description", feedModel.Description),
new SqliteParameter("language", feedModel.Language),
new SqliteParameter("copyright", feedModel.Copyright),
new SqliteParameter("dateAdded", feedModel.DateAdded.ToUnixTimeMilliseconds()),
new SqliteParameter("lastUpdated", feedModel.LastUpdated.ToUnixTimeMilliseconds()),
new SqliteParameter("imageUrl", feedModel.ImageUrl)
}
};
int affected = await cmd.ExecuteNonQueryAsync();
if (affected != 0)
result = true;
_sqlConn.Close();
return result;
}
// Feed item
private async void InitializeDb() private async void InitializeDb()
{ {
Log.Verbose("Checking database..."); Log.Verbose("Checking database...");
HashSet<string> failed = new HashSet<string>(); HashSet<string> failed = new HashSet<string>();
_sqlConn.Open(); _sqlConn.Open();
Log.Verbose("Checking table: {Table}", _groupTable); Log.Verbose("Checking table: {Table}", _groupTable);
var queryResponse = await _sqlConn.QueryAsync($"CREATE TABLE IF NOT EXISTS {_groupTable} (name STRING NOT NULL, hex_color STRING NOT NULL, icon STRING, id STRING PRIMARY KEY, CONSTRAINT name UNIQUE (name))"); var queryResponse = await _sqlConn.QueryAsync($"CREATE TABLE IF NOT EXISTS {_groupTable} (name STRING NOT NULL, hex_color STRING NOT NULL, icon STRING, id STRING PRIMARY KEY)");
if (queryResponse.Any()) failed.Add("category_data"); if (queryResponse.Any()) failed.Add("category_data");
Log.Verbose("Checking table: {Table}", _feedTable); Log.Verbose("Checking table: {Table}", _feedTable);
queryResponse = await _sqlConn.QueryAsync($"CREATE TABLE IF NOT EXISTS {_feedTable} (url STRING NOT NULL, id STRING PRIMARY KEY, group_id STRING DEFAULT NULL, CONSTRAINT url, UNIQUE (url))"); queryResponse = await _sqlConn.QueryAsync($"CREATE TABLE IF NOT EXISTS {_feedTable} (id STRING PRIMARY KEY, url STRING NOT NULL, group_id STRING DEFAULT NULL, feed_type STRING, description STRING, language STRING, copyright STRING, date_added INT, last_updated INT, image_url STRING)");
if (queryResponse.Any()) failed.Add("feed_data"); if (queryResponse.Any()) failed.Add("feed_data");
Log.Verbose("Checking table: {Table}", _feedItemTable); Log.Verbose("Checking table: {Table}", _feedItemTable);
queryResponse = await _sqlConn.QueryAsync($"CREATE TABLE IF NOT EXISTS {_feedItemTable} (id STRING PRIMARY KEY, feed_id STRING NOT NULL)"); queryResponse = await _sqlConn.QueryAsync($"CREATE TABLE IF NOT EXISTS {_feedItemTable} (id STRING PRIMARY KEY, feed_id STRING DEFAULT NULL, read INT, type STRING, title STRING, description STRING, link STRING, last_updated INT, publishing_date INT, author STRING, categories STRING, content STRING)");
if (queryResponse.Any()) failed.Add("feed_item_data"); if (queryResponse.Any()) failed.Add("feed_item_data");
_sqlConn.Close(); _sqlConn.Close();

View File

@ -15,13 +15,23 @@ namespace SharpRss.Services
{ {
public RssService() public RssService()
{ {
//SetupTestCategoriesAndFeedsAsync(); SetupTestCategoriesAndFeedsAsync();
} }
private readonly DatabaseService _dbService = new DatabaseService(); private readonly DatabaseService _dbService = new DatabaseService();
/*private async void SetupTestCategoriesAndFeedsAsync() private async void SetupTestCategoriesAndFeedsAsync()
{ {
await _dbService.AddCategoriesAsync(new HashSet<CategoryModel>() /*bool result = await _dbService.SetGroupAsync(new GroupModel() { Name = "News" });
result = await _dbService.SetGroupAsync(new GroupModel() { Name = "Tech" });
result = await _dbService.SetGroupAsync(new GroupModel() { Name = "Science" });
result = await _dbService.SetGroupAsync(new GroupModel() { Name = "Test" });*/
/*GroupModel? editGroup = await _dbService.GetGroupsAsync("Test");
if (editGroup != null)
{
bool result2 = await _dbService.RemoveGroupAsync(editGroup);
}*/
/*await _dbService.AddCategoriesAsync(new HashSet<CategoryModel>()
{ {
new CategoryModel() { Name = "All" }, new CategoryModel() { Name = "All" },
new CategoryModel() { Name = "RSS" }, new CategoryModel() { Name = "RSS" },
@ -34,59 +44,7 @@ namespace SharpRss.Services
new FeedModel("https://www.nasa.gov/rss/dyn/breaking_news.rss"), new FeedModel("https://www.nasa.gov/rss/dyn/breaking_news.rss"),
new FeedModel("https://journals.plos.org/plosone/feed/atom"), new FeedModel("https://journals.plos.org/plosone/feed/atom"),
new FeedModel("https://itsfoss.com/feed") new FeedModel("https://itsfoss.com/feed")
}); });*/
}*/
public async Task<Feed> GetFeedAsync(string rssUrl)
{
/*return await FeedCache.GetFeed(rssUrl);*/
return new Feed();
}
public async Task<HashSet<object>> GetAllUnsortedAsync()
{
HashSet<object> items = new HashSet<object>();
/*var categories = await _dbService.GetCategoriesAsync();
var feeds = await _dbService.GetFeedsAsync(string.Empty);
items.UnionWith(categories);
items.UnionWith(feeds);*/
return items;
}
public async Task<HashSet<GroupModel>> GetCategoriesAsync()
{
/*var result = await _dbService.GetCategoriesAsync();
return result.OrderBy(x => x.Name).ToHashSet();*/
return new HashSet<GroupModel>();
}
public async Task<HashSet<FeedModel>> GetFeedsAsync(GroupModel? categoryModel = null)
{
HashSet<FeedModel> feeds = new HashSet<FeedModel>();
/*if (categoryModel != null)
feeds = await _dbService.GetFeedsAsync(categoryModel.Id);
else
feeds = await _dbService.GetFeedsAsync();*/
return feeds;
}
public async void AddCategoryAsync(string name, string? icon, string hexColor)
{
/*GroupModel groupModel = new GroupModel()
{
Name = name
};
if (icon != null)
groupModel.PathIcon = icon;
if (!hexColor.IsNullEmptyWhiteSpace())
groupModel.HexColor = hexColor;*/
//await _dbService.AddCategoriesAsync(new HashSet<GroupModel>() { groupModel });
}
public async void AddFeedAsync(string rssUrl, GroupModel? category = null)
{
//FeedModel feedModel = new FeedModel(rssUrl, category);
//await _dbService.AddFeedsAsync(new HashSet<FeedModel>() { feedModel });
} }
public void Dispose() public void Dispose()

View File

@ -10,6 +10,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="CodeHollow.FeedReader" Version="1.2.6" /> <PackageReference Include="CodeHollow.FeedReader" Version="1.2.6" />
<PackageReference Include="Dapper" Version="2.0.123" /> <PackageReference Include="Dapper" Version="2.0.123" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.5" /> <PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.5" />
<PackageReference Include="Serilog" Version="2.12.0" /> <PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" /> <PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />

Binary file not shown.