[backend/masto-client] Add media upload (ISH-58)

This commit is contained in:
Laura Hausmann 2024-02-13 02:36:19 +01:00
parent 589063f36b
commit a1c23a7d29
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
5 changed files with 82 additions and 7 deletions

View file

@ -0,0 +1,47 @@
using Iceshrimp.Backend.Controllers.Mastodon.Attributes;
using Iceshrimp.Backend.Controllers.Mastodon.Schemas;
using Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
using Iceshrimp.Backend.Core.Middleware;
using Iceshrimp.Backend.Core.Services;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
namespace Iceshrimp.Backend.Controllers.Mastodon;
[MastodonApiController]
[Authenticate]
[Authorize("write:media")]
[EnableCors("mastodon")]
[EnableRateLimiting("sliding")]
[Produces("application/json")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Attachment))]
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
public class MediaController(DriveService driveSvc) : Controller {
[HttpPost("/api/v1/media")]
[HttpPost("/api/v2/media")]
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
public async Task<IActionResult> UploadAttachment(MediaSchemas.UploadMediaRequest request) {
var user = HttpContext.GetUserOrFail();
var rq = new DriveFileCreationRequest {
Filename = request.File.FileName,
IsSensitive = false,
Comment = request.Description,
MimeType = request.File.ContentType,
};
var file = await driveSvc.StoreFile(request.File.OpenReadStream(), user, rq);
var res = new Attachment {
Id = file.Id,
Type = Attachment.GetType(file.Type),
Url = file.Url,
Blurhash = file.Blurhash,
Description = file.Comment,
PreviewUrl = file.ThumbnailUrl,
RemoteUrl = file.Uri,
//Metadata = TODO
};
return Ok(res);
}
}

View file

@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Mvc;
namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas;
public abstract class MediaSchemas {
public class UploadMediaRequest {
[FromForm(Name = "file")] public required IFormFile File { get; set; }
[FromForm(Name = "description")] public string? Description { get; set; }
//TODO: add thumbnail & focus properties
}
}

View file

@ -92,8 +92,13 @@ public class StatusController(DatabaseContext db, NoteRenderer noteRenderer, Not
throw GracefulException.BadRequest("Reply target is nonexistent or inaccessible")
: null;
var note = await noteSvc.CreateNoteAsync(user, visibility, request.Text, request.Cw, reply);
var res = await noteRenderer.RenderAsync(note);
var attachments = request.MediaIds != null
? await db.DriveFiles.Where(p => request.MediaIds.Contains(p.Id)).ToListAsync()
: null;
var note = await noteSvc.CreateNoteAsync(user, visibility, request.Text, request.Cw, reply,
attachments: attachments);
var res = await noteRenderer.RenderAsync(note);
return Ok(res);
}

View file

@ -100,15 +100,19 @@ public class DriveService(
string? blurhash = null;
if (request.MimeType.StartsWith("image/")) {
if (request.MimeType.StartsWith("image/") || request.MimeType == "image") {
try {
var image = await Image.LoadAsync<Rgba32>(buf);
blurhash = Blurhasher.Encode(image, 7, 7);
// Correct mime type
if (request.MimeType == "image" && image.Metadata.DecodedImageFormat?.DefaultMimeType != null)
request.MimeType = image.Metadata.DecodedImageFormat.DefaultMimeType;
}
catch {
logger.LogError("Failed to generate blurhash for image with mime type {type}", request.MimeType);
}
buf.Seek(0, SeekOrigin.Begin);
}
@ -154,7 +158,7 @@ public class DriveService(
Url = url,
Name = request.Filename,
Comment = request.Comment,
Type = request.MimeType,
Type = CleanMimeType(request.MimeType),
RequestHeaders = request.RequestHeaders,
RequestIp = request.RequestIp,
Blurhash = blurhash,

View file

@ -39,8 +39,10 @@ public class NoteService(
private readonly List<string> _resolverHistory = [];
private int _recursionLimit = 100;
public async Task<Note> CreateNoteAsync(User user, Note.NoteVisibility visibility, string? text = null,
string? cw = null, Note? reply = null, Note? renote = null) {
public async Task<Note> CreateNoteAsync(
User user, Note.NoteVisibility visibility, string? text = null, string? cw = null, Note? reply = null,
Note? renote = null, IReadOnlyCollection<DriveFile>? attachments = null
) {
if (text?.Length > config.Value.CharacterLimit)
throw GracefulException.BadRequest($"Text cannot be longer than {config.Value.CharacterLimit} characters");
@ -52,6 +54,9 @@ public class NoteService(
if (text != null)
text = mentionsResolver.ResolveMentions(text, null, mentions, splitDomainMapping);
if (attachments != null && attachments.Any(p => p.UserId != user.Id))
throw GracefulException.BadRequest("Refusing to create note with files belonging to someone else");
var actor = await userRenderer.RenderAsync(user);
var note = new Note {
@ -65,6 +70,8 @@ public class NoteService(
UserHost = null,
Visibility = visibility,
FileIds = attachments?.Select(p => p.Id).ToList() ?? [],
AttachedFileTypes = attachments?.Select(p => p.Type).ToList() ?? [],
Mentions = mentionedUserIds,
VisibleUserIds = visibility == Note.NoteVisibility.Specified ? mentionedUserIds : [],
MentionedRemoteUsers = remoteMentions,