Iceshrimp.NET/Iceshrimp.Backend/Controllers/SearchController.cs

133 lines
No EOL
5.2 KiB
C#

using System.Diagnostics.CodeAnalysis;
using System.Net.Mime;
using Iceshrimp.Backend.Controllers.Attributes;
using Iceshrimp.Backend.Controllers.Renderers;
using Iceshrimp.Backend.Controllers.Schemas;
using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Middleware;
using Iceshrimp.Backend.Core.Services;
using Iceshrimp.Shared.Schemas;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
namespace Iceshrimp.Backend.Controllers;
[ApiController]
[Authenticate]
[Authorize]
[EnableRateLimiting("sliding")]
[Route("/api/iceshrimp/search")]
[Produces(MediaTypeNames.Application.Json)]
public class SearchController(
DatabaseContext db,
NoteService noteSvc,
NoteRenderer noteRenderer,
UserRenderer userRenderer,
ActivityPub.UserResolver userResolver,
IOptions<Config.InstanceSection> config
)
: ControllerBase
{
[HttpGet("notes")]
[LinkPagination(20, 80)]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List<NoteResponse>))]
public async Task<IActionResult> SearchNotes([FromQuery(Name = "q")] string query, PaginationQuery pagination)
{
var user = HttpContext.GetUserOrFail();
var notes = await db.Notes
.IncludeCommonProperties()
.FilterByFtsQuery(query, user, db)
.EnsureVisibleFor(user)
.FilterHidden(user, db)
.Paginate(pagination, ControllerContext)
.PrecomputeVisibilities(user)
.ToListAsync();
return Ok(await noteRenderer.RenderMany(notes.EnforceRenoteReplyVisibility(), user));
}
[HttpGet("users")]
[LinkPagination(20, 80)]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List<UserResponse>))]
[SuppressMessage("ReSharper", "EntityFramework.UnsupportedServerSideFunctionCall",
Justification = "Inspection doesn't know about the Projectable attribute")]
public async Task<IActionResult> SearchUsers([FromQuery(Name = "q")] string query, PaginationQuery pagination)
{
var users = await db.Users
.IncludeCommonProperties()
.Where(p => p.DisplayNameOrUsernameOrFqnContainsCaseInsensitive(query,
config.Value.AccountDomain))
.Paginate(pagination, ControllerContext) //TODO: this will mess up our sorting
.OrderByDescending(p => p.NotesCount)
.ToListAsync();
return Ok(await userRenderer.RenderMany(users));
}
[HttpGet("lookup")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(RedirectResponse))]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ErrorResponse))]
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(ErrorResponse))]
public async Task<IActionResult> Lookup([FromQuery(Name = "target")] string target)
{
target = target.Trim();
var instancePrefix = $"https://{config.Value.WebDomain}";
var notePrefix = $"{instancePrefix}/notes/";
var userPrefix = $"{instancePrefix}/users/";
var userPrefixAlt = $"{instancePrefix}/@";
if (target.StartsWith('@') || target.StartsWith(userPrefixAlt))
{
var hit = await userResolver.ResolveAsyncOrNull(target);
if (hit != null) return Ok(new RedirectResponse { TargetUrl = $"/users/{hit.Id}" });
throw GracefulException.NotFound("No result found");
}
if (target.StartsWith("http://") || target.StartsWith("https://"))
{
Note? noteHit = null;
User? userHit = null;
if (target.StartsWith(notePrefix))
{
noteHit = await db.Notes.FirstOrDefaultAsync(p => p.Id == target.Substring(notePrefix.Length));
if (noteHit == null)
throw GracefulException.NotFound("No result found");
}
else if (target.StartsWith(userPrefix))
{
userHit = await db.Users.FirstOrDefaultAsync(p => p.Id == target.Substring(userPrefix.Length));
if (userHit == null)
throw GracefulException.NotFound("No result found");
}
else if (target.StartsWith(instancePrefix))
{
if (userHit == null)
throw GracefulException.NotFound("No result found");
}
noteHit ??= await db.Notes.FirstOrDefaultAsync(p => p.Uri == target || p.Url == target);
if (noteHit != null) return Ok(new RedirectResponse { TargetUrl = $"/notes/{noteHit.Id}" });
userHit ??= await db.Users.FirstOrDefaultAsync(p => p.Uri == target ||
(p.UserProfile != null &&
p.UserProfile.Url == target));
if (userHit != null) return Ok(new RedirectResponse { TargetUrl = $"/users/{userHit.Id}" });
noteHit = await noteSvc.ResolveNoteAsync(target);
if (noteHit != null) return Ok(new RedirectResponse { TargetUrl = $"/notes/{noteHit.Id}" });
userHit = await userResolver.ResolveAsyncOrNull(target);
if (userHit != null) return Ok(new RedirectResponse { TargetUrl = $"/users/{userHit.Id}" });
throw GracefulException.NotFound("No result found");
}
throw GracefulException.BadRequest("Invalid lookup target");
}
}