[backend/masto-client] Add media upload (ISH-58)
This commit is contained in:
parent
589063f36b
commit
a1c23a7d29
5 changed files with 82 additions and 7 deletions
47
Iceshrimp.Backend/Controllers/Mastodon/MediaController.cs
Normal file
47
Iceshrimp.Backend/Controllers/Mastodon/MediaController.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -92,8 +92,13 @@ public class StatusController(DatabaseContext db, NoteRenderer noteRenderer, Not
|
||||||
throw GracefulException.BadRequest("Reply target is nonexistent or inaccessible")
|
throw GracefulException.BadRequest("Reply target is nonexistent or inaccessible")
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
var note = await noteSvc.CreateNoteAsync(user, visibility, request.Text, request.Cw, reply);
|
var attachments = request.MediaIds != null
|
||||||
var res = await noteRenderer.RenderAsync(note);
|
? 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);
|
return Ok(res);
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,15 +100,19 @@ public class DriveService(
|
||||||
|
|
||||||
string? blurhash = null;
|
string? blurhash = null;
|
||||||
|
|
||||||
if (request.MimeType.StartsWith("image/")) {
|
if (request.MimeType.StartsWith("image/") || request.MimeType == "image") {
|
||||||
try {
|
try {
|
||||||
var image = await Image.LoadAsync<Rgba32>(buf);
|
var image = await Image.LoadAsync<Rgba32>(buf);
|
||||||
blurhash = Blurhasher.Encode(image, 7, 7);
|
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 {
|
catch {
|
||||||
logger.LogError("Failed to generate blurhash for image with mime type {type}", request.MimeType);
|
logger.LogError("Failed to generate blurhash for image with mime type {type}", request.MimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.Seek(0, SeekOrigin.Begin);
|
buf.Seek(0, SeekOrigin.Begin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +158,7 @@ public class DriveService(
|
||||||
Url = url,
|
Url = url,
|
||||||
Name = request.Filename,
|
Name = request.Filename,
|
||||||
Comment = request.Comment,
|
Comment = request.Comment,
|
||||||
Type = request.MimeType,
|
Type = CleanMimeType(request.MimeType),
|
||||||
RequestHeaders = request.RequestHeaders,
|
RequestHeaders = request.RequestHeaders,
|
||||||
RequestIp = request.RequestIp,
|
RequestIp = request.RequestIp,
|
||||||
Blurhash = blurhash,
|
Blurhash = blurhash,
|
||||||
|
|
|
@ -39,8 +39,10 @@ public class NoteService(
|
||||||
private readonly List<string> _resolverHistory = [];
|
private readonly List<string> _resolverHistory = [];
|
||||||
private int _recursionLimit = 100;
|
private int _recursionLimit = 100;
|
||||||
|
|
||||||
public async Task<Note> CreateNoteAsync(User user, Note.NoteVisibility visibility, string? text = null,
|
public async Task<Note> CreateNoteAsync(
|
||||||
string? cw = null, Note? reply = null, Note? renote = null) {
|
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)
|
if (text?.Length > config.Value.CharacterLimit)
|
||||||
throw GracefulException.BadRequest($"Text cannot be longer than {config.Value.CharacterLimit} characters");
|
throw GracefulException.BadRequest($"Text cannot be longer than {config.Value.CharacterLimit} characters");
|
||||||
|
|
||||||
|
@ -52,6 +54,9 @@ public class NoteService(
|
||||||
if (text != null)
|
if (text != null)
|
||||||
text = mentionsResolver.ResolveMentions(text, null, mentions, splitDomainMapping);
|
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 actor = await userRenderer.RenderAsync(user);
|
||||||
|
|
||||||
var note = new Note {
|
var note = new Note {
|
||||||
|
@ -65,6 +70,8 @@ public class NoteService(
|
||||||
UserHost = null,
|
UserHost = null,
|
||||||
Visibility = visibility,
|
Visibility = visibility,
|
||||||
|
|
||||||
|
FileIds = attachments?.Select(p => p.Id).ToList() ?? [],
|
||||||
|
AttachedFileTypes = attachments?.Select(p => p.Type).ToList() ?? [],
|
||||||
Mentions = mentionedUserIds,
|
Mentions = mentionedUserIds,
|
||||||
VisibleUserIds = visibility == Note.NoteVisibility.Specified ? mentionedUserIds : [],
|
VisibleUserIds = visibility == Note.NoteVisibility.Specified ? mentionedUserIds : [],
|
||||||
MentionedRemoteUsers = remoteMentions,
|
MentionedRemoteUsers = remoteMentions,
|
||||||
|
|
Loading…
Add table
Reference in a new issue