aboutsummaryrefslogtreecommitdiff
path: root/back-end/src/Model/BookmarkStore.cs
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-02-25 01:11:06 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2024-02-25 01:11:06 -0500
commitbd3a7a25792b837c5f28c7580adf132abc6f35e7 (patch)
tree2a3ec046f8f76f115e648f2bc6d1576cfa0a6c6f /back-end/src/Model/BookmarkStore.cs
parent52645b724834e669788a45edb9d135f243432540 (diff)
Squashed commit of the following:
commit 069f81fc3c87c437eceff756ddca7a4c1b58044d Author: vnugent <public@vaughnnugent.com> Date: Sat Feb 24 22:33:34 2024 -0500 feat: #3 setup mode, admin signup, fixes, and contianerize! commit 97ffede9eb312fca0257afa06969d47a12703f3b Author: vnugent <public@vaughnnugent.com> Date: Mon Feb 19 22:26:03 2024 -0500 feat: new account setup and invitation links commit 1c8f59bc0a1b25ce5013b0f1fc7fa73c0de415d6 Author: vnugent <public@vaughnnugent.com> Date: Thu Feb 15 16:49:59 2024 -0500 feat: update packages, drag/drop link, and fix some button padding
Diffstat (limited to 'back-end/src/Model/BookmarkStore.cs')
-rw-r--r--back-end/src/Model/BookmarkStore.cs88
1 files changed, 65 insertions, 23 deletions
diff --git a/back-end/src/Model/BookmarkStore.cs b/back-end/src/Model/BookmarkStore.cs
index 8578976..ec020e8 100644
--- a/back-end/src/Model/BookmarkStore.cs
+++ b/back-end/src/Model/BookmarkStore.cs
@@ -22,15 +22,18 @@ using System.Threading.Tasks;
using System.Collections.Generic;
using VNLib.Utils;
+using VNLib.Plugins;
using VNLib.Plugins.Extensions.Data;
using VNLib.Plugins.Extensions.Loading;
using VNLib.Plugins.Extensions.Data.Abstractions;
-
+using VNLib.Plugins.Extensions.Loading.Sql;
namespace SimpleBookmark.Model
{
- internal sealed class BookmarkStore(IAsyncLazy<DbContextOptions> dbOptions) : DbStore<BookmarkEntry>
+ internal sealed class BookmarkStore(PluginBase plugin) : DbStore<BookmarkEntry>
{
+ private readonly IAsyncLazy<DbContextOptions> dbOptions = plugin.GetContextOptionsAsync();
+
///<inheritdoc/>
public override IDbQueryLookup<BookmarkEntry> QueryTable { get; } = new BookmarkQueryLookup();
@@ -52,6 +55,8 @@ namespace SimpleBookmark.Model
public async Task<BookmarkEntry[]> SearchBookmarksAsync(string userId, string? query, string[] tags, int limit, int page, CancellationToken cancellation)
{
+ BookmarkEntry[] results;
+
ArgumentNullException.ThrowIfNull(userId);
ArgumentNullException.ThrowIfNull(tags);
ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(limit, 0);
@@ -59,31 +64,56 @@ namespace SimpleBookmark.Model
//Init new db connection
await using SimpleBookmarkContext context = new(dbOptions.Value);
- await context.OpenTransactionAsync(cancellation);
- //Start with userid
+ //Build the query starting with the user's bookmarks
IQueryable<BookmarkEntry> q = context.Bookmarks.Where(b => b.UserId == userId);
+ //reduce result set by search query first
+ if (!string.IsNullOrWhiteSpace(query))
+ {
+ q = WithSearch(q, query);
+ }
+
+ q = q.OrderByDescending(static b => b.Created);
+
+ /*
+ * For some databases server-side tag filtering is not supported.
+ * Client side evaluation must be used to finally filter the results.
+ *
+ * I am attempting to reduce the result set as much as possible on server-side
+ * evaluation before pulling the results into memory. So search, ordering and
+ * first tag filtering is done on server-side. The final tag filtering is done
+ * for multiple tags on client-side along with pagination. For bookmarks, I expect
+ * the result set to at worst double digits for most users, so this should be fine.
+ *
+ */
+
if (tags.Length > 0)
{
- //if tags are set, only return bookmarks that match the tags
- q = q.Where(b => b.Tags != null && tags.All(t => b.Tags!.Contains(t)));
+
+ //filter out bookmarks that do not have any tags and reduce by the first tag
+ q = q.Where(static b => b.Tags != null && b.Tags.Length > 0)
+ .Where(b => EF.Functions.Like(b.Tags, $"%{tags[0]}%"));
}
- if (!string.IsNullOrWhiteSpace(query))
+ if(tags.Length > 1)
{
- //if query is set, only return bookmarks that match the query
- q = q.Where(b => EF.Functions.Like(b.Name, $"%{query}%") || EF.Functions.Like(b.Description, $"%{query}%"));
+ //Finally pull all results into memory
+ BookmarkEntry[] bookmarkEntries = await q.ToArrayAsync(cancellation);
+
+ //filter out bookmarks that do not have all requested tags, then skip and take the requested page
+ results = bookmarkEntries.Where(b => tags.All(p => b.JsonTags!.Contains(p)))
+ .Skip(page * limit)
+ .Take(limit)
+ .ToArray();
}
-
- //return bookmarks in descending order of creation
- q = q.OrderByDescending(static b => b.Created);
-
- //return only the requested page
- q = q.Skip(page * limit).Take(limit);
-
- //execute query
- BookmarkEntry[] results = await q.ToArrayAsync(cancellation);
+ else
+ {
+ //execute server-side query
+ results = await q.Skip(page * limit)
+ .Take(limit)
+ .ToArrayAsync(cancellation);
+ }
//Close db and commit transaction
await context.SaveAndCloseAsync(true, cancellation);
@@ -91,14 +121,19 @@ namespace SimpleBookmark.Model
return results;
}
+ private static IQueryable<BookmarkEntry> WithSearch(IQueryable<BookmarkEntry> q, string query)
+ {
+ //if query is set, only return bookmarks that match the query
+ return q.Where(b => EF.Functions.Like(b.Name, $"%{query}%") || EF.Functions.Like(b.Description, $"%{query}%"));
+ }
+
public async Task<string[]> GetAllTagsForUserAsync(string userId, CancellationToken cancellation)
{
ArgumentNullException.ThrowIfNull(userId);
//Init new db connection
await using SimpleBookmarkContext context = new(dbOptions.Value);
- await context.OpenTransactionAsync(cancellation);
-
+
//Get all tags for the user
string[] tags = await context.Bookmarks
.Where(b => b.UserId == userId)
@@ -116,11 +151,19 @@ namespace SimpleBookmark.Model
.ToArray();
}
+ public async Task<ERRNO> DeleteAllForUserAsync(string userId, CancellationToken cancellation)
+ {
+ await using SimpleBookmarkContext context = new(dbOptions.Value);
+
+ context.Bookmarks.RemoveRange(context.Bookmarks.Where(b => b.UserId == userId));
+
+ return await context.SaveAndCloseAsync(true, cancellation);
+ }
+
public async Task<ERRNO> AddBulkAsync(IEnumerable<BookmarkEntry> bookmarks, string userId, DateTimeOffset now, CancellationToken cancellation)
{
//Init new db connection
await using SimpleBookmarkContext context = new(dbOptions.Value);
- await context.OpenTransactionAsync(cancellation);
//Setup clean bookmark instances
bookmarks = bookmarks.Select(b => new BookmarkEntry
@@ -162,8 +205,7 @@ namespace SimpleBookmark.Model
public IQueryable<BookmarkEntry> GetSingleQueryBuilder(IDbContextHandle context, params string[] constraints)
{
string bookmarkId = constraints[0];
- string userId = constraints[1];
-
+ string userId = constraints[1];
return from b in context.Set<BookmarkEntry>()
where b.UserId == userId && b.Id == bookmarkId