[backend/masto-client] Refactor mastodon entity names to prevent class name conflicts

This commit is contained in:
Laura Hausmann 2024-02-17 00:20:52 +01:00
parent 5bd92d14ac
commit 7fcf9a5179
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
27 changed files with 194 additions and 206 deletions

View file

@ -13,7 +13,6 @@ using Microsoft.EntityFrameworkCore;
using Iceshrimp.Backend.Controllers.Mastodon.Renderers;
using Iceshrimp.Backend.Core.Services;
using Microsoft.AspNetCore.Cors;
using Notification = Iceshrimp.Backend.Core.Database.Tables.Notification;
namespace Iceshrimp.Backend.Controllers.Mastodon;
@ -33,7 +32,7 @@ public class AccountController(
) : Controller {
[HttpGet("verify_credentials")]
[Authorize("read:accounts")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Account))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AccountEntity))]
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
public async Task<IActionResult> VerifyUserCredentials() {
@ -43,7 +42,7 @@ public class AccountController(
}
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Account))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AccountEntity))]
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
public async Task<IActionResult> GetUser(string id) {
var user = await db.Users.IncludeCommonProperties().FirstOrDefaultAsync(p => p.Id == id)
@ -54,7 +53,7 @@ public class AccountController(
[HttpPost("{id}/follow")]
[Authorize("write:follows")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Relationship))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(RelationshipEntity))]
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
//TODO: [FromHybrid] request (bool reblogs, bool notify, bool languages)
@ -129,7 +128,7 @@ public class AccountController(
followee.PrecomputedIsFollowedBy = true;
}
var res = new Relationship {
var res = new RelationshipEntity {
Id = followee.Id,
Following = followee.PrecomputedIsFollowedBy ?? false,
FollowedBy = followee.PrecomputedIsFollowing ?? false,
@ -151,7 +150,7 @@ public class AccountController(
[HttpPost("{id}/unfollow")]
[Authorize("write:follows")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Relationship))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(RelationshipEntity))]
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
public async Task<IActionResult> UnfollowUser(string id) {
@ -195,7 +194,7 @@ public class AccountController(
p.Notifiee == followee && p.Notifier == user))
.ExecuteDeleteAsync();
var res = new Relationship {
var res = new RelationshipEntity {
Id = followee.Id,
Following = followee.PrecomputedIsFollowedBy ?? false,
FollowedBy = followee.PrecomputedIsFollowing ?? false,
@ -217,7 +216,7 @@ public class AccountController(
[HttpGet("relationships")]
[Authorize("read:follows")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Relationship[]))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(RelationshipEntity[]))]
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
public async Task<IActionResult> GetRelationships([FromQuery(Name = "id")] List<string> ids) {
@ -228,7 +227,7 @@ public class AccountController(
.PrecomputeRelationshipData(user)
.ToListAsync();
var res = users.Select(u => new Relationship {
var res = users.Select(u => new RelationshipEntity {
Id = u.Id,
Following = u.PrecomputedIsFollowedBy ?? false,
FollowedBy = u.PrecomputedIsFollowing ?? false,
@ -253,7 +252,7 @@ public class AccountController(
[Authorize("read:statuses")]
[LinkPagination(20, 40)]
[Produces("application/json")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<Status>))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<StatusEntity>))]
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
public async Task<IActionResult> GetUserStatuses(string id, AccountSchemas.AccountStatusesRequest request,
@ -276,7 +275,7 @@ public class AccountController(
[HttpGet("{id}/followers")]
[Authenticate("read:accounts")]
[LinkPagination(40, 80)]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<Account>))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<AccountEntity>))]
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
public async Task<IActionResult> GetUserFollowers(string id, PaginationQuery query) {
var user = HttpContext.GetUser();
@ -286,10 +285,10 @@ public class AccountController(
if (user == null || user.Id != account.Id) {
if (account.UserProfile?.FFVisibility == UserProfile.UserProfileFFVisibility.Private)
return Ok((List<Account>) []);
return Ok((List<AccountEntity>) []);
if (account.UserProfile?.FFVisibility == UserProfile.UserProfileFFVisibility.Followers)
if (user == null || !await db.Users.AnyAsync(p => p == account && p.Followers.Contains(user)))
return Ok((List<Account>) []);
return Ok((List<AccountEntity>) []);
}
var res = await db.Users
@ -305,7 +304,7 @@ public class AccountController(
[HttpGet("{id}/following")]
[Authenticate("read:accounts")]
[LinkPagination(40, 80)]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<Account>))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<AccountEntity>))]
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
public async Task<IActionResult> GetUserFollowing(string id, PaginationQuery query) {
var user = HttpContext.GetUser();
@ -315,10 +314,10 @@ public class AccountController(
if (user == null || user.Id != account.Id) {
if (account.UserProfile?.FFVisibility == UserProfile.UserProfileFFVisibility.Private)
return Ok((List<Account>) []);
return Ok((List<AccountEntity>) []);
if (account.UserProfile?.FFVisibility == UserProfile.UserProfileFFVisibility.Followers)
if (user == null || !await db.Users.AnyAsync(p => p == account && p.Followers.Contains(user)))
return Ok((List<Account>) []);
return Ok((List<AccountEntity>) []);
}
var res = await db.Users
@ -334,7 +333,7 @@ public class AccountController(
[HttpGet("/api/v1/follow_requests")]
[Authorize("read:follows")]
[LinkPagination(40, 80)]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<Account>))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<AccountEntity>))]
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
public async Task<IActionResult> GetFollowRequests(PaginationQuery query) {
@ -351,7 +350,7 @@ public class AccountController(
[HttpPost("/api/v1/follow_requests/{id}/authorize")]
[Authorize("write:follows")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Relationship))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(RelationshipEntity))]
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
@ -402,7 +401,7 @@ public class AccountController(
var relationship = await db.Users.Where(p => id == p.Id)
.IncludeCommonProperties()
.PrecomputeRelationshipData(user)
.Select(u => new Relationship {
.Select(u => new RelationshipEntity {
Id = u.Id,
Following = u.PrecomputedIsFollowedBy ?? false,
FollowedBy = u.PrecomputedIsFollowing ?? false,
@ -428,7 +427,7 @@ public class AccountController(
[HttpPost("/api/v1/follow_requests/{id}/reject")]
[Authorize("write:follows")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Relationship))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(RelationshipEntity))]
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
@ -462,7 +461,7 @@ public class AccountController(
var relationship = await db.Users.Where(p => id == p.Id)
.IncludeCommonProperties()
.PrecomputeRelationshipData(user)
.Select(u => new Relationship {
.Select(u => new RelationshipEntity {
Id = u.Id,
Following = u.PrecomputedIsFollowedBy ?? false,
FollowedBy = u.PrecomputedIsFollowing ?? false,

View file

@ -1,4 +1,5 @@
using Iceshrimp.Backend.Controllers.Mastodon.Attributes;
using Iceshrimp.Backend.Controllers.Mastodon.Schemas;
using Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Database;
@ -23,7 +24,7 @@ public class InstanceController(DatabaseContext db) : Controller {
var instanceCount = await db.Instances.LongCountAsync();
//TODO: admin contact
var res = new InstanceInfo(config.Value) {
var res = new InstanceInfoResponse(config.Value) {
Stats = new InstanceStats(userCount, noteCount, instanceCount)
};

View file

@ -15,7 +15,7 @@ namespace Iceshrimp.Backend.Controllers.Mastodon;
[EnableCors("mastodon")]
[EnableRateLimiting("sliding")]
[Produces("application/json")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Attachment))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AttachmentEntity))]
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
public class MediaController(DriveService driveSvc) : Controller {
@ -31,9 +31,9 @@ public class MediaController(DriveService driveSvc) : Controller {
MimeType = request.File.ContentType,
};
var file = await driveSvc.StoreFile(request.File.OpenReadStream(), user, rq);
var res = new Attachment {
var res = new AttachmentEntity {
Id = file.Id,
Type = Attachment.GetType(file.Type),
Type = AttachmentEntity.GetType(file.Type),
Url = file.Url,
Blurhash = file.Blurhash,
Description = file.Comment,

View file

@ -24,7 +24,7 @@ public class NotificationController(DatabaseContext db, NotificationRenderer not
[Authorize("read:notifications")]
[LinkPagination(40, 80)]
[Produces("application/json")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List<Notification>))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List<NotificationEntity>))]
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
public async Task<IActionResult> GetNotifications(PaginationQuery query) {
var user = HttpContext.GetUserOrFail();

View file

@ -15,9 +15,9 @@ public class NoteRenderer(
MfmConverter mfmConverter,
DatabaseContext db
) {
public async Task<Status> RenderAsync(
Note note, User? user, List<Account>? accounts = null, List<Mention>? mentions = null,
List<Attachment>? attachments = null, Dictionary<string, int>? likeCounts = null,
public async Task<StatusEntity> RenderAsync(
Note note, User? user, List<AccountEntity>? accounts = null, List<MentionEntity>? mentions = null,
List<AttachmentEntity>? attachments = null, Dictionary<string, int>? likeCounts = null,
List<string>? likedNotes = null, int recurse = 2
) {
var uri = note.Uri ?? note.GetPublicUri(config.Value);
@ -32,7 +32,7 @@ public class NoteRenderer(
if (mentions == null) {
mentions = await db.Users.IncludeCommonProperties()
.Where(p => note.Mentions.Contains(p.Id))
.Select(u => new Mention(u, config.Value.WebDomain))
.Select(u => new MentionEntity(u, config.Value.WebDomain))
.ToListAsync();
}
else {
@ -41,7 +41,7 @@ public class NoteRenderer(
if (attachments == null) {
attachments = await db.DriveFiles.Where(p => note.FileIds.Contains(p.Id))
.Select(f => new Attachment {
.Select(f => new AttachmentEntity {
Id = f.Id,
Url = f.WebpublicUrl ?? f.Url,
Blurhash = f.Blurhash,
@ -49,7 +49,7 @@ public class NoteRenderer(
Description = f.Comment,
Metadata = null,
RemoteUrl = f.Uri,
Type = Attachment.GetType(f.Type)
Type = AttachmentEntity.GetType(f.Type)
})
.ToListAsync();
}
@ -70,7 +70,7 @@ public class NoteRenderer(
var account = accounts?.FirstOrDefault(p => p.Id == note.UserId) ?? await userRenderer.RenderAsync(note.User);
var res = new Status {
var res = new StatusEntity {
Id = note.Id,
Uri = uri,
Url = note.Url ?? uri,
@ -91,7 +91,7 @@ public class NoteRenderer(
IsMuted = null, //FIXME
IsSensitive = note.Cw != null,
ContentWarning = note.Cw ?? "",
Visibility = Status.EncodeVisibility(note.Visibility),
Visibility = StatusEntity.EncodeVisibility(note.Visibility),
Content = content,
Text = text,
Mentions = mentions,
@ -102,18 +102,18 @@ public class NoteRenderer(
return res;
}
private async Task<List<Mention>> GetMentions(IEnumerable<Note> notes) {
private async Task<List<MentionEntity>> GetMentions(IEnumerable<Note> notes) {
var ids = notes.SelectMany(n => n.Mentions).Distinct();
return await db.Users.IncludeCommonProperties()
.Where(p => ids.Contains(p.Id))
.Select(u => new Mention(u, config.Value.WebDomain))
.Select(u => new MentionEntity(u, config.Value.WebDomain))
.ToListAsync();
}
private async Task<List<Attachment>> GetAttachments(IEnumerable<Note> notes) {
private async Task<List<AttachmentEntity>> GetAttachments(IEnumerable<Note> notes) {
var ids = notes.SelectMany(n => n.FileIds).Distinct();
return await db.DriveFiles.Where(p => ids.Contains(p.Id))
.Select(f => new Attachment {
.Select(f => new AttachmentEntity {
Id = f.Id,
Url = f.Url,
Blurhash = f.Blurhash,
@ -121,12 +121,12 @@ public class NoteRenderer(
Description = f.Comment,
Metadata = null,
RemoteUrl = f.Uri,
Type = Attachment.GetType(f.Type)
Type = AttachmentEntity.GetType(f.Type)
})
.ToListAsync();
}
internal async Task<List<Account>> GetAccounts(IEnumerable<User> users) {
internal async Task<List<AccountEntity>> GetAccounts(IEnumerable<User> users) {
return (await userRenderer.RenderManyAsync(users.DistinctBy(p => p.Id))).ToList();
}
@ -142,8 +142,8 @@ public class NoteRenderer(
.ToListAsync();
}
public async Task<IEnumerable<Status>> RenderManyAsync(
IEnumerable<Note> notes, User? user, List<Account>? accounts = null
public async Task<IEnumerable<StatusEntity>> RenderManyAsync(
IEnumerable<Note> notes, User? user, List<AccountEntity>? accounts = null
) {
var noteList = notes.ToList();
accounts ??= await GetAccounts(noteList.Select(p => p.User));

View file

@ -2,14 +2,12 @@ using Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Middleware;
using Notification = Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities.Notification;
using DbNotification = Iceshrimp.Backend.Core.Database.Tables.Notification;
namespace Iceshrimp.Backend.Controllers.Mastodon.Renderers;
public class NotificationRenderer(NoteRenderer noteRenderer, UserRenderer userRenderer) {
public async Task<Notification> RenderAsync(
DbNotification notification, User? user, List<Account>? accounts = null, IEnumerable<Status>? statuses = null
public async Task<NotificationEntity> RenderAsync(
Notification notification, User? user, List<AccountEntity>? accounts = null, IEnumerable<StatusEntity>? statuses = null
) {
var dbNotifier = notification.Notifier ?? throw new GracefulException("Notification has no notifier");
@ -23,9 +21,9 @@ public class NotificationRenderer(NoteRenderer noteRenderer, UserRenderer userRe
//TODO: specially handle quotes
var res = new Notification {
var res = new NotificationEntity {
Id = notification.Id,
Type = Notification.EncodeType(notification.Type),
Type = NotificationEntity.EncodeType(notification.Type),
Note = note,
Notifier = notifier,
CreatedAt = notification.CreatedAt.ToStringMastodon()
@ -34,8 +32,8 @@ public class NotificationRenderer(NoteRenderer noteRenderer, UserRenderer userRe
return res;
}
public async Task<IEnumerable<Notification>> RenderManyAsync(
IEnumerable<DbNotification> notifications, User? user
public async Task<IEnumerable<NotificationEntity>> RenderManyAsync(
IEnumerable<Notification> notifications, User? user
) {
var notificationList = notifications.ToList();

View file

@ -10,12 +10,12 @@ namespace Iceshrimp.Backend.Controllers.Mastodon.Renderers;
public class UserRenderer(IOptions<Config.InstanceSection> config, MfmConverter mfmConverter) {
private readonly string _transparent = $"https://{config.Value.WebDomain}/assets/transparent.png";
public async Task<Account> RenderAsync(User user, UserProfile? profile) {
public async Task<AccountEntity> RenderAsync(User user, UserProfile? profile) {
var acct = user.Username;
if (user.Host != null)
acct += $"@{user.Host}";
var res = new Account {
var res = new AccountEntity {
Id = user.Id,
DisplayName = user.DisplayName ?? user.Username,
AvatarUrl = user.AvatarUrl ?? user.GetIdenticonUrl(config.Value),
@ -41,11 +41,11 @@ public class UserRenderer(IOptions<Config.InstanceSection> config, MfmConverter
return res;
}
public async Task<Account> RenderAsync(User user) {
public async Task<AccountEntity> RenderAsync(User user) {
return await RenderAsync(user, user.UserProfile);
}
public async Task<IEnumerable<Account>> RenderManyAsync(IEnumerable<User> users) {
public async Task<IEnumerable<AccountEntity>> RenderManyAsync(IEnumerable<User> users) {
return await users.Select(RenderAsync).AwaitAllAsync();
}
}

View file

@ -1,36 +0,0 @@
using Iceshrimp.Backend.Core.Database;
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
public class Account : IEntity {
[J("username")] public required string Username { get; set; }
[J("acct")] public required string Acct { get; set; }
[J("fqn")] public required string FullyQualifiedName { get; set; }
[J("display_name")] public required string DisplayName { get; set; }
[J("locked")] public required bool IsLocked { get; set; }
[J("created_at")] public required string CreatedAt { get; set; }
[J("followers_count")] public required long FollowersCount { get; set; }
[J("following_count")] public required long FollowingCount { get; set; }
[J("statuses_count")] public required long StatusesCount { get; set; }
[J("note")] public required string Note { get; set; }
[J("url")] public required string Url { get; set; }
[J("avatar")] public required string AvatarUrl { get; set; }
[J("avatar_static")] public required string AvatarStaticUrl { get; set; }
[J("header")] public required string HeaderUrl { get; set; }
[J("header_static")] public required string HeaderStaticUrl { get; set; }
[J("moved")] public required Account? MovedToAccount { get; set; }
[J("bot")] public required bool IsBot { get; set; }
[J("discoverable")] public required bool IsDiscoverable { get; set; }
[J("fields")] public required Field[] Fields { get; set; }
[J("source")] public object? Source => null; //FIXME
[J("emojis")] public object[] Emoji => []; //FIXME
[J("id")] public required string Id { get; set; }
}
public class Field {
[J("name")] public required string Name { get; set; }
[J("value")] public required string Value { get; set; }
[J("verified_at")] public string? VerifiedAt { get; set; }
}

View file

@ -0,0 +1,36 @@
using Iceshrimp.Backend.Core.Database;
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
public class AccountEntity : IEntity {
[J("username")] public required string Username { get; set; }
[J("acct")] public required string Acct { get; set; }
[J("fqn")] public required string FullyQualifiedName { get; set; }
[J("display_name")] public required string DisplayName { get; set; }
[J("locked")] public required bool IsLocked { get; set; }
[J("created_at")] public required string CreatedAt { get; set; }
[J("followers_count")] public required long FollowersCount { get; set; }
[J("following_count")] public required long FollowingCount { get; set; }
[J("statuses_count")] public required long StatusesCount { get; set; }
[J("note")] public required string Note { get; set; }
[J("url")] public required string Url { get; set; }
[J("avatar")] public required string AvatarUrl { get; set; }
[J("avatar_static")] public required string AvatarStaticUrl { get; set; }
[J("header")] public required string HeaderUrl { get; set; }
[J("header_static")] public required string HeaderStaticUrl { get; set; }
[J("moved")] public required AccountEntity? MovedToAccount { get; set; }
[J("bot")] public required bool IsBot { get; set; }
[J("discoverable")] public required bool IsDiscoverable { get; set; }
[J("fields")] public required Field[] Fields { get; set; }
[J("source")] public object? Source => null; //FIXME
[J("emojis")] public object[] Emoji => []; //FIXME
[J("id")] public required string Id { get; set; }
}
public class Field {
[J("name")] public required string Name { get; set; }
[J("value")] public required string Value { get; set; }
[J("verified_at")] public string? VerifiedAt { get; set; }
}

View file

@ -2,7 +2,7 @@ using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
public class Attachment {
public class AttachmentEntity {
[J("id")] public required string Id { get; set; }
[J("url")] public required string Url { get; set; }
[J("remote_url")] public string? RemoteUrl { get; set; }

View file

@ -5,7 +5,7 @@ using JI = System.Text.Json.Serialization.JsonIgnoreAttribute;
namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
public class Mention() {
public class MentionEntity() {
[J("id")] public required string Id { get; set; }
[J("username")] public required string Username { get; set; }
[J("acct")] public required string Acct { get; set; }
@ -16,7 +16,7 @@ public class Mention() {
[JI] public required string? Host;
[SetsRequiredMembers]
public Mention(User u, string webDomain) : this() {
public MentionEntity(User u, string webDomain) : this() {
Id = u.Id;
Username = u.Username;
Host = u.Host;

View file

@ -5,12 +5,12 @@ using static Iceshrimp.Backend.Core.Database.Tables.Notification;
namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
public class Notification : IEntity {
public class NotificationEntity : IEntity {
[J("id")] public required string Id { get; set; }
[J("created_at")] public required string CreatedAt { get; set; }
[J("type")] public required string Type { get; set; }
[J("account")] public required Account Notifier { get; set; }
[J("status")] public required Status? Note { get; set; }
[J("account")] public required AccountEntity Notifier { get; set; }
[J("status")] public required StatusEntity? Note { get; set; }
//TODO: [J("reaction")] public required Reaction? Reaction { get; set; }

View file

@ -5,7 +5,7 @@ using JI = System.Text.Json.Serialization.JsonIgnoreAttribute;
namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
public class Relationship : IEntity {
public class RelationshipEntity : IEntity {
[J("following")] public required bool Following { get; set; }
[J("followed_by")] public required bool FollowedBy { get; set; }
[J("blocking")] public required bool Blocking { get; set; }

View file

@ -1,77 +0,0 @@
using System.Text.Json.Serialization;
using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Middleware;
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
using JI = System.Text.Json.Serialization.JsonIgnoreAttribute;
namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
public class Status : IEntity {
[J("text")] [JI(Condition = JsonIgnoreCondition.WhenWritingNull)]
public required string? Text;
[J("uri")] public required string Uri { get; set; }
[J("url")] public required string Url { get; set; }
[J("account")] public required Account Account { get; set; }
[J("in_reply_to_id")] public required string? ReplyId { get; set; }
[J("in_reply_to_account_id")] public required string? ReplyUserId { get; set; }
[J("reblog")] public required Status? Renote { get; set; }
[J("quote")] public required Status? Quote { get; set; }
[J("content_type")] public required string ContentType { get; set; }
[J("created_at")] public required string CreatedAt { get; set; }
[J("edited_at")] public required string? EditedAt { get; set; }
[J("replies_count")] public required long RepliesCount { get; set; }
[J("reblogs_count")] public required long RenoteCount { get; set; }
[J("favourites_count")] public required long FavoriteCount { get; set; }
[J("reblogged")] public required bool? IsRenoted { get; set; }
[J("favourited")] public required bool? IsFavorited { get; set; }
[J("bookmarked")] public required bool? IsBookmarked { get; set; }
[J("muted")] public required bool? IsMuted { get; set; }
[J("sensitive")] public required bool IsSensitive { get; set; }
[J("spoiler_text")] public required string ContentWarning { get; set; }
[J("visibility")] public required string Visibility { get; set; }
[J("content")]
[JI(Condition = JsonIgnoreCondition.WhenWritingNull)]
public required string? Content { get; set; }
[J("pinned")]
[JI(Condition = JsonIgnoreCondition.WhenWritingNull)]
public required bool? IsPinned { get; set; }
[J("mentions")] public required List<Mention> Mentions { get; set; }
[J("media_attachments")] public required List<Attachment> Attachments { get; set; }
[J("emojis")] public object[] Emojis => []; //FIXME
[J("tags")] public object[] Tags => []; //FIXME
[J("reactions")] public object[] Reactions => []; //FIXME
[J("filtered")] public object[] Filtered => []; //FIXME
[J("card")] public object? Card => null; //FIXME
[J("poll")] public object? Poll => null; //FIXME
[J("application")] public object? Application => null; //FIXME
[J("language")] public string? Language => null; //FIXME
[J("id")] public required string Id { get; set; }
public static string EncodeVisibility(Note.NoteVisibility visibility) {
return visibility switch {
Note.NoteVisibility.Public => "public",
Note.NoteVisibility.Home => "unlisted",
Note.NoteVisibility.Followers => "private",
Note.NoteVisibility.Specified => "direct",
_ => throw new GracefulException($"Unknown visibility: {visibility}")
};
}
public static Note.NoteVisibility DecodeVisibility(string visibility) {
return visibility switch {
"public" => Note.NoteVisibility.Public,
"unlisted" => Note.NoteVisibility.Home,
"private" => Note.NoteVisibility.Followers,
"direct" => Note.NoteVisibility.Specified,
_ => throw GracefulException.BadRequest($"Unknown visibility: {visibility}")
};
}
}

View file

@ -1,8 +0,0 @@
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
public class StatusContext {
[J("ancestors")] public required List<Status> Ancestors { get; set; }
[J("descendants")] public required List<Status> Descendants { get; set; }
}

View file

@ -0,0 +1,82 @@
using System.Text.Json.Serialization;
using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Middleware;
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
using JI = System.Text.Json.Serialization.JsonIgnoreAttribute;
namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
public class StatusEntity : IEntity {
[J("text")] [JI(Condition = JsonIgnoreCondition.WhenWritingNull)]
public required string? Text;
[J("uri")] public required string Uri { get; set; }
[J("url")] public required string Url { get; set; }
[J("account")] public required AccountEntity Account { get; set; }
[J("in_reply_to_id")] public required string? ReplyId { get; set; }
[J("in_reply_to_account_id")] public required string? ReplyUserId { get; set; }
[J("reblog")] public required StatusEntity? Renote { get; set; }
[J("quote")] public required StatusEntity? Quote { get; set; }
[J("content_type")] public required string ContentType { get; set; }
[J("created_at")] public required string CreatedAt { get; set; }
[J("edited_at")] public required string? EditedAt { get; set; }
[J("replies_count")] public required long RepliesCount { get; set; }
[J("reblogs_count")] public required long RenoteCount { get; set; }
[J("favourites_count")] public required long FavoriteCount { get; set; }
[J("reblogged")] public required bool? IsRenoted { get; set; }
[J("favourited")] public required bool? IsFavorited { get; set; }
[J("bookmarked")] public required bool? IsBookmarked { get; set; }
[J("muted")] public required bool? IsMuted { get; set; }
[J("sensitive")] public required bool IsSensitive { get; set; }
[J("spoiler_text")] public required string ContentWarning { get; set; }
[J("visibility")] public required string Visibility { get; set; }
[J("content")]
[JI(Condition = JsonIgnoreCondition.WhenWritingNull)]
public required string? Content { get; set; }
[J("pinned")]
[JI(Condition = JsonIgnoreCondition.WhenWritingNull)]
public required bool? IsPinned { get; set; }
[J("mentions")] public required List<MentionEntity> Mentions { get; set; }
[J("media_attachments")] public required List<AttachmentEntity> Attachments { get; set; }
[J("emojis")] public object[] Emojis => []; //FIXME
[J("tags")] public object[] Tags => []; //FIXME
[J("reactions")] public object[] Reactions => []; //FIXME
[J("filtered")] public object[] Filtered => []; //FIXME
[J("card")] public object? Card => null; //FIXME
[J("poll")] public object? Poll => null; //FIXME
[J("application")] public object? Application => null; //FIXME
[J("language")] public string? Language => null; //FIXME
[J("id")] public required string Id { get; set; }
public static string EncodeVisibility(Note.NoteVisibility visibility) {
return visibility switch {
Note.NoteVisibility.Public => "public",
Note.NoteVisibility.Home => "unlisted",
Note.NoteVisibility.Followers => "private",
Note.NoteVisibility.Specified => "direct",
_ => throw new GracefulException($"Unknown visibility: {visibility}")
};
}
public static Note.NoteVisibility DecodeVisibility(string visibility) {
return visibility switch {
"public" => Note.NoteVisibility.Public,
"unlisted" => Note.NoteVisibility.Home,
"private" => Note.NoteVisibility.Followers,
"direct" => Note.NoteVisibility.Specified,
_ => throw GracefulException.BadRequest($"Unknown visibility: {visibility}")
};
}
}
public class StatusContext {
[J("ancestors")] public required List<StatusEntity> Ancestors { get; set; }
[J("descendants")] public required List<StatusEntity> Descendants { get; set; }
}

View file

@ -1,9 +1,9 @@
using Iceshrimp.Backend.Core.Configuration;
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas;
public class InstanceInfo(Config config) {
public class InstanceInfoResponse(Config config) {
[J("version")] public string Version => $"4.2.1 (compatible; Iceshrimp.NET/{config.Instance.Version}";
[J("max_toot_chars")] public int MaxNoteChars => config.Instance.CharacterLimit;

View file

@ -17,8 +17,8 @@ public abstract class SearchSchemas {
}
public class SearchResponse {
[J("accounts")] public required List<Account> Accounts { get; set; }
[J("statuses")] public required List<Status> Statuses { get; set; }
[J("accounts")] public required List<AccountEntity> Accounts { get; set; }
[J("statuses")] public required List<StatusEntity> Statuses { get; set; }
[J("hashtags")] public List<object> Hashtags => []; //TODO: implement this
}
}

View file

@ -49,7 +49,7 @@ public class SearchController(
[SuppressMessage("ReSharper", "EntityFramework.UnsupportedServerSideFunctionCall",
Justification = "Inspection doesn't know about the Projectable attribute")]
private async Task<List<Account>> SearchUsersAsync(SearchSchemas.SearchRequest search, PaginationQuery pagination) {
private async Task<List<AccountEntity>> SearchUsersAsync(SearchSchemas.SearchRequest search, PaginationQuery pagination) {
var user = HttpContext.GetUserOrFail();
if (search.Resolve) {
@ -105,7 +105,7 @@ public class SearchController(
[SuppressMessage("ReSharper", "EntityFramework.UnsupportedServerSideFunctionCall",
Justification = "Inspection doesn't know about the Projectable attribute")]
private async Task<List<Status>> SearchNotesAsync(SearchSchemas.SearchRequest search, PaginationQuery pagination) {
private async Task<List<StatusEntity>> SearchNotesAsync(SearchSchemas.SearchRequest search, PaginationQuery pagination) {
var user = HttpContext.GetUserOrFail();
if (search.Resolve && (search.Query!.StartsWith("https://") || search.Query.StartsWith("http://"))) {

View file

@ -29,7 +29,7 @@ public class StatusController(
[HttpGet("{id}")]
[Authenticate("read:statuses")]
[Produces("application/json")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Status))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(StatusEntity))]
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
public async Task<IActionResult> GetNote(string id) {
var user = HttpContext.GetUser();
@ -47,7 +47,7 @@ public class StatusController(
[HttpGet("{id}/context")]
[Authenticate("read:statuses")]
[Produces("application/json")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Status))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(StatusEntity))]
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
public async Task<IActionResult> GetStatusContext(string id) {
var user = HttpContext.GetUser();
@ -81,7 +81,7 @@ public class StatusController(
[HttpPost("{id}/favourite")]
[Authorize("write:favourites")]
[Produces("application/json")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Status))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(StatusEntity))]
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
@ -100,7 +100,7 @@ public class StatusController(
[HttpPost("{id}/unfavourite")]
[Authorize("write:favourites")]
[Produces("application/json")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Status))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(StatusEntity))]
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
@ -119,7 +119,7 @@ public class StatusController(
[HttpPost]
[Authorize("write:statuses")]
[Produces("application/json")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Status))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(StatusEntity))]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(MastodonErrorResponse))]
public async Task<IActionResult> PostNote([FromHybrid] StatusSchemas.PostStatusRequest request) {
var user = HttpContext.GetUserOrFail();
@ -151,7 +151,7 @@ public class StatusController(
if (request.Poll != null)
throw GracefulException.BadRequest("Polls haven't been implemented yet");
var visibility = Status.DecodeVisibility(request.Visibility);
var visibility = StatusEntity.DecodeVisibility(request.Visibility);
var reply = request.ReplyId != null
? await db.Notes.Where(p => p.Id == request.ReplyId).EnsureVisibleFor(user).FirstOrDefaultAsync() ??
throw GracefulException.BadRequest("Reply target is nonexistent or inaccessible")

View file

@ -27,7 +27,7 @@ public class TimelineController(DatabaseContext db, NoteRenderer noteRenderer, I
[Authorize("read:statuses")]
[HttpGet("home")]
[Produces("application/json")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<Status>))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<StatusEntity>))]
public async Task<IActionResult> GetHomeTimeline(PaginationQuery query) {
var user = HttpContext.GetUserOrFail();
var heuristic = await QueryableExtensions.GetHeuristic(user, db, cache);
@ -49,7 +49,7 @@ public class TimelineController(DatabaseContext db, NoteRenderer noteRenderer, I
[Authorize("read:statuses")]
[HttpGet("public")]
[Produces("application/json")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<Status>))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<StatusEntity>))]
public async Task<IActionResult> GetPublicTimeline(PaginationQuery query) {
var user = HttpContext.GetUserOrFail();

View file

@ -9,7 +9,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Notification = Iceshrimp.Backend.Core.Database.Tables.Notification;
#nullable disable

View file

@ -9,7 +9,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Notification = Iceshrimp.Backend.Core.Database.Tables.Notification;
#nullable disable

View file

@ -9,7 +9,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Notification = Iceshrimp.Backend.Core.Database.Tables.Notification;
#nullable disable

View file

@ -9,7 +9,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Notification = Iceshrimp.Backend.Core.Database.Tables.Notification;
#nullable disable

View file

@ -8,7 +8,6 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Notification = Iceshrimp.Backend.Core.Database.Tables.Notification;
#nullable disable

View file

@ -10,8 +10,6 @@ using Iceshrimp.Backend.Core.Middleware;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
using MastoNotification = Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities.Notification;
using Notification = Iceshrimp.Backend.Core.Database.Tables.Notification;
namespace Iceshrimp.Backend.Core.Extensions;
@ -218,7 +216,7 @@ public static class QueryableExtensions {
return list.Select(EnforceRenoteReplyVisibility);
}
public static async Task<List<Status>> RenderAllForMastodonAsync(
public static async Task<List<StatusEntity>> RenderAllForMastodonAsync(
this IQueryable<Note> notes, NoteRenderer renderer, User? user
) {
var list = (await notes.ToListAsync())
@ -226,13 +224,13 @@ public static class QueryableExtensions {
return (await renderer.RenderManyAsync(list, user)).ToList();
}
public static async Task<List<Account>> RenderAllForMastodonAsync(
public static async Task<List<AccountEntity>> RenderAllForMastodonAsync(
this IQueryable<User> users, UserRenderer renderer) {
var list = await users.ToListAsync();
return (await renderer.RenderManyAsync(list)).ToList();
}
public static async Task<List<MastoNotification>> RenderAllForMastodonAsync(
public static async Task<List<NotificationEntity>> RenderAllForMastodonAsync(
this IQueryable<Notification> notifications, NotificationRenderer renderer, User? user) {
var list = await notifications.ToListAsync();
return (await renderer.RenderManyAsync(list, user)).ToList();