diff --git a/Iceshrimp.Backend/Controllers/Mastodon/AccountController.cs b/Iceshrimp.Backend/Controllers/Mastodon/AccountController.cs index ec78ba7b..e5ca9e39 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/AccountController.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/AccountController.cs @@ -1,3 +1,4 @@ +using EntityFrameworkCore.Projectables; using Iceshrimp.Backend.Controllers.Attributes; using Iceshrimp.Backend.Controllers.Mastodon.Attributes; using Iceshrimp.Backend.Controllers.Mastodon.Schemas; @@ -186,7 +187,7 @@ public class AccountController( return Ok(res); } - + [HttpGet("relationships")] [Authorize("read:follows")] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Relationship[]))] @@ -231,12 +232,15 @@ public class AccountController( [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))] [ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))] - public async Task GetHomeTimeline(string id, PaginationQuery query) { - //TODO: there's a lot more query params to implement here - var user = HttpContext.GetUserOrFail(); + public async Task GetUserStatuses(string id, AccountSchemas.AccountStatusesRequest request, + PaginationQuery query) { + var user = HttpContext.GetUserOrFail(); + var account = await db.Users.FirstOrDefaultAsync(p => p.Id == id) ?? throw GracefulException.RecordNotFound(); + var res = await db.Notes .IncludeCommonProperties() - .FilterByUser(id) + .FilterByUser(account) + .FilterByAccountStatusesRequest(request, account) .EnsureVisibleFor(user) .Paginate(query, ControllerContext) .PrecomputeVisibilities(user) diff --git a/Iceshrimp.Backend/Controllers/Mastodon/Schemas/AccountSchemas.cs b/Iceshrimp.Backend/Controllers/Mastodon/Schemas/AccountSchemas.cs new file mode 100644 index 00000000..8d6fbf33 --- /dev/null +++ b/Iceshrimp.Backend/Controllers/Mastodon/Schemas/AccountSchemas.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas; + +public abstract class AccountSchemas { + public class AccountStatusesRequest { + [FromQuery(Name = "only_media")] public bool OnlyMedia { get; set; } = false; + [FromQuery(Name = "exclude_replies")] public bool ExcludeReplies { get; set; } = false; + [FromQuery(Name = "exclude_reblogs")] public bool ExcludeRenotes { get; set; } = false; + [FromQuery(Name = "pinned")] public bool Pinned { get; set; } = false; + [FromQuery(Name = "tagged")] public string? Tagged { get; set; } + } +} \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Database/Tables/User.cs b/Iceshrimp.Backend/Core/Database/Tables/User.cs index 2f8c6c17..d4975952 100644 --- a/Iceshrimp.Backend/Core/Database/Tables/User.cs +++ b/Iceshrimp.Backend/Core/Database/Tables/User.cs @@ -445,6 +445,8 @@ public class User : IEntity { [InverseProperty(nameof(UserNotePin.User))] public virtual ICollection UserNotePins { get; set; } = new List(); + [Projectable] public virtual IEnumerable PinnedNotes => UserNotePins.Select(p => p.Note); + [InverseProperty(nameof(Tables.UserProfile.User))] public virtual UserProfile? UserProfile { get; set; } @@ -477,7 +479,7 @@ public class User : IEntity { [Projectable] public bool DisplayNameContainsCaseInsensitive(string str) => DisplayName != null && EF.Functions.ILike(DisplayName, "%" + EfHelpers.EscapeLikeQuery(str) + "%", @"\"); - + [Projectable] public bool UsernameContainsCaseInsensitive(string str) => DisplayName != null && EF.Functions.ILike(DisplayName, "%" + EfHelpers.EscapeLikeQuery(str) + "%", @"\"); @@ -506,6 +508,9 @@ public class User : IEntity { [Projectable] public bool IsMuting(User user) => Muting.Contains(user); + [Projectable] + public bool HasPinned(Note note) => PinnedNotes.Contains(note); + public User WithPrecomputedBlockStatus(bool blocking, bool blockedBy) { PrecomputedIsBlocking = blocking; PrecomputedIsBlockedBy = blockedBy; diff --git a/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs b/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs index 7d2dc636..bec98ea3 100644 --- a/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs +++ b/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs @@ -75,11 +75,11 @@ public static class NoteQueryableExtensions { public static IQueryable FilterByFollowingAndOwn(this IQueryable query, User user) { return query.Where(note => note.User == user || note.User.IsFollowedBy(user)); } - + public static IQueryable FilterByUser(this IQueryable query, User user) { return query.Where(note => note.User == user); } - + public static IQueryable FilterByUser(this IQueryable query, string userId) { return query.Where(note => note.UserId == userId); } @@ -125,7 +125,7 @@ public static class NoteQueryableExtensions { return query.Where(note => !note.User.UserListMembers.Any(p => p.UserList.User == user && p.UserList.HideFromHomeTl)); } - + public static Note EnforceRenoteReplyVisibility(this Note note) { if (!note.PrecomputedIsReplyVisible ?? false) note.Reply = null; @@ -145,10 +145,27 @@ public static class NoteQueryableExtensions { .EnforceRenoteReplyVisibility(); return (await renderer.RenderManyAsync(list)).ToList(); } - + public static async Task> RenderAllForMastodonAsync( this IQueryable users, UserRenderer renderer) { var list = await users.ToListAsync(); return (await renderer.RenderManyAsync(list)).ToList(); } + + public static IQueryable FilterByAccountStatusesRequest(this IQueryable query, + AccountSchemas.AccountStatusesRequest request, + User account) { + if (request.ExcludeReplies) + query = query.Where(p => p.Reply == null); + if (request.ExcludeRenotes) + query = query.Where(p => p.Renote == null); + if (request.Tagged != null) + query = query.Where(p => p.Tags.Contains(request.Tagged.ToLowerInvariant())); + if (request.OnlyMedia) + query = query.Where(p => p.FileIds.Count != 0); + if (request.Pinned) + query = query.Where(note => account.HasPinned(note)); + + return query; + } } \ No newline at end of file