| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139 |
- using Application.Abstractions.Data;
- using Application.Abstractions.Messaging;
- using Application.Features.Api.Feed.Shared;
- using Microsoft.EntityFrameworkCore;
- namespace Application.Features.Api.Feed.GetForYou;
- public sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
- {
- public async Task<Response> Handle(Query request, CancellationToken ct)
- {
- var followingIDs = await db.MemberFollow.AsNoTracking()
- .Where(f => f.FollowerMemberID == request.MemberID && !f.IsMuted)
- .Select(f => f.FolloweeMemberID)
- .ToListAsync(ct);
- var since = DateTime.UtcNow.AddDays(-14);
- var take = Math.Max(20, request.PerPage * 3);
- var followingRows = followingIDs.Count > 0
- ? await QueryRowsAsync(since, take, p => followingIDs.Contains(p.MemberID), byRecency: true, ct)
- : [];
- var trendingRows = await QueryRowsAsync(since, take, p => !followingIDs.Contains(p.MemberID), byRecency: false, ct);
- var merged = new List<Row>();
- var seen = new HashSet<int>();
- var followTake = (int)Math.Round((followingRows.Count + trendingRows.Count) * 0.7);
- foreach (var r in followingRows.Take(followTake))
- {
- if (seen.Add(r.ID))
- {
- merged.Add(r);
- }
- }
- foreach (var r in trendingRows)
- {
- if (seen.Add(r.ID))
- {
- merged.Add(r);
- }
- }
- merged = [..merged.OrderByDescending(r => r.CreatedAt)];
- var total = merged.Count;
- var skip = (request.Page - 1) * request.PerPage;
- var list = merged.Skip(skip).Take(request.PerPage).Select(r => new FeedCardRow
- {
- PostID = r.ID,
- AuthorSID = r.AuthorSID,
- AuthorName = r.AuthorName,
- AuthorThumb = r.AuthorThumb,
- AuthorSummary = r.AuthorSummary,
- IsCreator = r.IsCreator,
- ChannelSID = r.ChannelSID,
- Subject = FeedContent.Excerpt(r.Content, 60),
- ContentExcerpt = FeedContent.Excerpt(r.Content, 200),
- Thumbnail = r.Thumbnail,
- Images = r.Images,
- Medias = r.Medias,
- Likes = r.Likes,
- Comments = r.Comments,
- Bookmarks = r.Bookmarks,
- Views = r.Views,
- Tags = [..r.Tags.Select(t => new FeedCardRow.TagRef { Name = t.Name, Slug = t.Slug })],
- CreatedAt = r.CreatedAt
- }).ToList();
- return new Response { Total = total, List = list };
- }
- private async Task<List<Row>> QueryRowsAsync(
- DateTime since,
- int take,
- System.Linq.Expressions.Expression<Func<Domain.Entities.Feed.FeedPost, bool>> additionalFilter,
- bool byRecency,
- CancellationToken ct
- ) {
- var query = db.FeedPost.AsNoTracking()
- .Where(p => !p.IsDeleted && p.CreatedAt >= since)
- .Where(additionalFilter);
- var ordered = byRecency
- ? query.OrderByDescending(p => p.CreatedAt)
- : query.OrderByDescending(p => p.Likes + p.Comments * 2 + p.Views * 0.1).ThenByDescending(p => p.CreatedAt);
- return await ordered.Take(take).Select(p => new Row
- {
- ID = p.ID,
- AuthorSID = p.Member.SID,
- AuthorName = p.Member.Name,
- AuthorThumb = p.Member.Thumb,
- AuthorSummary = p.Member.Summary,
- IsCreator = p.Member.IsCreator,
- ChannelSID = p.Member.Channel != null ? p.Member.Channel.SID : null,
- Content = p.Content,
- Thumbnail = p.Thumbnail,
- Images = p.Images,
- Medias = p.Medias,
- Likes = p.Likes,
- Comments = p.Comments,
- Bookmarks = p.Bookmarks,
- Views = p.Views,
- Tags = p.FeedPostTag.Select(pt => new TagRow { Name = pt.FeedTag.Name, Slug = pt.FeedTag.Slug }).ToList(),
- CreatedAt = p.CreatedAt
- }).ToListAsync(ct);
- }
- private sealed class Row
- {
- public int ID { get; set; }
- public string? AuthorSID { get; set; }
- public string? AuthorName { get; set; }
- public string? AuthorThumb { get; set; }
- public string? AuthorSummary { get; set; }
- public bool IsCreator { get; set; }
- public string? ChannelSID { get; set; }
- public string Content { get; set; } = default!;
- public string? Thumbnail { get; set; }
- public byte Images { get; set; }
- public byte Medias { get; set; }
- public int Likes { get; set; }
- public int Comments { get; set; }
- public int Bookmarks { get; set; }
- public int Views { get; set; }
- public DateTime CreatedAt { get; set; }
- public List<TagRow> Tags { get; set; } = [];
- }
- private sealed class TagRow
- {
- public string Name { get; set; } = default!;
- public string Slug { get; set; } = default!;
- }
- }
|