summaryrefslogtreecommitdiff
path: root/Services/FeedService.cs
diff options
context:
space:
mode:
authorJake Mannens <jake@asger.xyz>2026-03-25 01:57:19 +1100
committerJake Mannens <jake@asger.xyz>2026-03-25 01:57:46 +1100
commit6c06dfc4f83f30292e65c08a3cb0c48401d4bfa7 (patch)
tree511f88873fa6173637115a38c31ec5f8018e108e /Services/FeedService.cs
parentc751709b1b4fe6f16fd84647e8e071455e7b78d6 (diff)
v0.2av0.2a
Diffstat (limited to 'Services/FeedService.cs')
-rw-r--r--Services/FeedService.cs155
1 files changed, 155 insertions, 0 deletions
diff --git a/Services/FeedService.cs b/Services/FeedService.cs
new file mode 100644
index 0000000..864a751
--- /dev/null
+++ b/Services/FeedService.cs
@@ -0,0 +1,155 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace HyperBooru.Services;
+
+public interface IFeedService {
+ public Media[] LoadChunk(
+ bool selectIngest,
+ bool includeNsfw,
+ Media? key = null,
+ int count = 50);
+
+ public Media[] LoadChunk(
+ bool selectIngest,
+ bool includeNsfw,
+ string query,
+ Media? key = null,
+ int count = 50);
+
+ public Media[] LoadChunk(
+ bool selectIngest,
+ bool includeNsfw,
+ Guid tagId,
+ Media? key = null,
+ int count = 50);
+}
+
+public class FeedService : IFeedService {
+ private IDbContextFactory<HBContext> dbFactory;
+
+ public FeedService(IDbContextFactory<HBContext> dbFactory) =>
+ this.dbFactory = dbFactory;
+
+ public Media[] LoadChunk(
+ bool selectIngest,
+ bool includeNsfw,
+ Media? key,
+ int count) => LoadChunkInternal(selectIngest, includeNsfw, null, null, key, count);
+
+ public Media[] LoadChunk(
+ bool selectIngest,
+ bool includeNsfw,
+ string query,
+ Media? key,
+ int count) => LoadChunkInternal(selectIngest, includeNsfw, query, null, key, count);
+
+ public Media[] LoadChunk(
+ bool selectIngest,
+ bool includeNsfw,
+ Guid tagId,
+ Media? key,
+ int count) => LoadChunkInternal(selectIngest, includeNsfw, null, tagId, key, count);
+
+ private Media[] LoadChunkInternal(
+ bool selectIngest,
+ bool includeNsfw,
+ string? query,
+ Guid? tagId,
+ Media? key,
+ int count) {
+
+ if(selectIngest && !includeNsfw)
+ return Array.Empty<Media>();
+
+ using var db = dbFactory.CreateDbContext();
+
+ IQueryable<Media> media = db.Media
+ .AsSingleQuery()
+ .AsNoTracking()
+ .Include(m => m.Tags)
+ .Include(m => m.CurrentUploadedFile);
+
+ if(!includeNsfw)
+ media = media
+ .Where(m => !TagsThatImply(db, HBContext.NsfwTag)
+ .Intersect(m.Tags.Select(t => t.TagDefinitionId))
+ .Any());
+
+ if(selectIngest) {
+ media = media
+ .Where(m => m.Tags
+ .Select(t => t.TagDefinitionId)
+ .Contains((int) HBObjectId.IngestTag));
+ } else {
+ media = media
+ .Where(m => !m.Tags
+ .Select(t => t.TagDefinitionId)
+ .Contains((int) HBObjectId.IngestTag));
+ }
+
+ if(query is not null) {
+ media = Search(media, query);
+ } else if(tagId is not null) {
+ media = media
+ .Where(m => TagsThatImply(db, (Guid) tagId)
+ .Intersect(m.Tags.Select(t => t.TagDefinitionId))
+ .Any());
+ }
+
+ if(key is not null)
+ media = media.Where(m => m.ObjectId > key.ObjectId);
+
+ return media
+ .OrderBy(m => m.ObjectId)
+ .Take(count)
+ .ToArray();
+ }
+
+ private static IQueryable<Media> Search(IQueryable<Media> media, string query) {
+ // TODO: search implicit tags as well
+
+ query = query.ToLower().Trim();
+
+ return media
+ .Where(m =>
+ (m.ShortDescription != null && m.ShortDescription.ToLower().Contains(query)) ||
+ (m.LongDescription != null && m.LongDescription.ToLower().Contains(query)) ||
+ (m.UploadedFiles.Any(uf => uf.Filename != null && uf.Filename.ToLower().Contains(query))) ||
+ (m.OcrData != null && m.OcrData.SearchableText.ToLower().Contains(query)) ||
+ (m.Tags.Any(t => t.TagDefinition.Name.ToLower().Contains(query))));
+ }
+
+ private static IQueryable<int> TagsThatImply(HBContext db, Guid tagId) =>
+ db.Database.SqlQueryRaw<int>("""
+ WITH RECURSIVE basetag AS (
+ SELECT "ObjectId" FROM "Objects" WHERE "Guid" = {0}
+ ),
+ impliedtags AS (
+ SELECT
+ "TagDefinitionObjectId"
+ FROM
+ "TagDefinitionTagDefinition"
+ INNER JOIN
+ basetag
+ ON
+ "ImplicitTagsObjectId" = basetag."ObjectId"
+ UNION
+ SELECT
+ "TagDefinitionTagDefinition"."TagDefinitionObjectId"
+ FROM
+ "TagDefinitionTagDefinition"
+ INNER JOIN
+ impliedtags
+ ON
+ impliedtags."TagDefinitionObjectId" = "TagDefinitionTagDefinition"."ImplicitTagsObjectId"
+ )
+ SELECT DISTINCT
+ "TagDefinitionObjectId" AS "Value"
+ FROM impliedtags
+ UNION
+ SELECT
+ "ObjectId" AS "Value"
+ FROM
+ basetag
+ """, tagId);
+}