using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.Mime; using Iceshrimp.Backend.Controllers.Mastodon.Attributes; using Iceshrimp.Backend.Controllers.Shared.Attributes; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Extensions; using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.Backend.Core.Services; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.RateLimiting; using Microsoft.EntityFrameworkCore; namespace Iceshrimp.Backend.Controllers.Mastodon; [MastodonApiController] [Route("/api/v1")] [Authenticate] [EnableCors("mastodon")] [EnableRateLimiting("sliding")] [Produces(MediaTypeNames.Application.Json)] public class BiteController(DatabaseContext db, BiteService biteSvc) : ControllerBase { [HttpPost("bite")] [Authenticate("write:bites")] [ProducesResults(HttpStatusCode.OK)] [ProducesErrors(HttpStatusCode.BadRequest, HttpStatusCode.NotFound)] public async Task BiteUser([FromHybrid] string id) { var user = HttpContext.GetUserOrFail(); if (user.Id == id) throw GracefulException.BadRequest("You cannot bite yourself"); var target = await db.Users.IncludeCommonProperties().FirstOrDefaultAsync(p => p.Id == id) ?? throw GracefulException.NotFound("User not found"); await biteSvc.BiteAsync(user, target); } [HttpPost("users/{id}/bite")] [Authenticate("write:bites")] [ProducesResults(HttpStatusCode.OK)] [ProducesErrors(HttpStatusCode.BadRequest, HttpStatusCode.NotFound)] public async Task BiteUser2(string id) { var user = HttpContext.GetUserOrFail(); if (user.Id == id) throw GracefulException.BadRequest("You cannot bite yourself"); var target = await db.Users.IncludeCommonProperties().FirstOrDefaultAsync(p => p.Id == id) ?? throw GracefulException.NotFound("User not found"); await biteSvc.BiteAsync(user, target); } [HttpPost("users/{id}/bite_back")] [Authenticate("write:bites")] [ProducesResults(HttpStatusCode.OK)] [ProducesErrors(HttpStatusCode.BadRequest, HttpStatusCode.NotFound)] [SuppressMessage("ReSharper", "EntityFramework.NPlusOne.IncompleteDataQuery")] [SuppressMessage("ReSharper", "EntityFramework.NPlusOne.IncompleteDataUsage")] public async Task BiteBack(string id) { var user = HttpContext.GetUserOrFail(); var target = await db.Bites .IncludeCommonProperties() .Where(p => p.Id == id) .FirstOrDefaultAsync() ?? throw GracefulException.NotFound("Bite not found"); if (user.Id != (target.TargetUserId ?? target.TargetNote?.UserId ?? target.TargetBite?.UserId)) throw GracefulException.BadRequest("You can only bite back at a user who bit you"); await biteSvc.BiteAsync(user, target); } [HttpPost("statuses/{id}/bite")] [Authenticate("write:bites")] [ProducesResults(HttpStatusCode.OK)] [ProducesErrors(HttpStatusCode.BadRequest, HttpStatusCode.NotFound)] public async Task BiteStatus(string id) { var user = HttpContext.GetUserOrFail(); if (user.Id == id) throw GracefulException.BadRequest("You cannot bite your own note"); var target = await db.Notes .Where(p => p.Id == id) .IncludeCommonProperties() .EnsureVisibleFor(user) .FirstOrDefaultAsync() ?? throw GracefulException.NotFound("Note not found"); await biteSvc.BiteAsync(user, target); } }