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 dbFactory; public FeedService(IDbContextFactory 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(); using var db = dbFactory.CreateDbContext(); IQueryable 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 Search(IQueryable 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 TagsThatImply(HBContext db, Guid tagId) => db.Database.SqlQueryRaw(""" 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); }