[backend] Code cleanup

This commit is contained in:
Laura Hausmann 2024-03-17 13:36:08 +01:00
parent b7584674c7
commit a408fa247a
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
62 changed files with 274 additions and 209 deletions

View file

@ -55,7 +55,9 @@ public class NodeInfoController(IOptions<Config.InstanceSection> config, Databas
//FIXME Implement members
Users = new NodeInfoResponse.NodeInfoUsers
{
Total = totalUsers, ActiveMonth = activeMonth, ActiveHalfYear = activeHalfYear
Total = totalUsers,
ActiveMonth = activeMonth,
ActiveHalfYear = activeHalfYear
},
LocalComments = 0,
LocalPosts = localPosts

View file

@ -6,19 +6,19 @@ namespace Iceshrimp.Backend.Controllers.Federation.Schemas;
[XmlRoot("XRD", Namespace = "http://docs.oasis-open.org/ns/xri/xrd-1.0", IsNullable = false)]
public class HostMetaXmlResponse()
{
[XmlElement("Link")] public required HostMetaXmlResponseLink Link;
[SetsRequiredMembers]
public HostMetaXmlResponse(string webDomain) : this() => Link = new HostMetaXmlResponseLink(webDomain);
[XmlElement("Link")] public required HostMetaXmlResponseLink Link;
}
public class HostMetaXmlResponseLink()
{
[XmlAttribute("rel")] public string Rel = "lrdd";
[XmlAttribute("template")] public required string Template;
[XmlAttribute("type")] public string Type = "application/xrd+xml";
[SetsRequiredMembers]
public HostMetaXmlResponseLink(string webDomain) : this() =>
Template = $"https://{webDomain}/.well-known/webfinger?resource={{uri}}";
[XmlAttribute("rel")] public string Rel = "lrdd";
[XmlAttribute("type")] public string Type = "application/xrd+xml";
[XmlAttribute("template")] public required string Template;
}

View file

@ -59,7 +59,9 @@ public class WellKnownController(IOptions<Config.InstanceSection> config, Databa
[
new WebFingerLink
{
Rel = "self", Type = "application/activity+json", Href = user.GetPublicUri(config.Value)
Rel = "self",
Type = "application/activity+json",
Href = user.GetPublicUri(config.Value)
},
new WebFingerLink
{

View file

@ -76,7 +76,12 @@ public class AccountController(
if (request.Fields?.Where(p => p is { Name: not null, Value: not null }).ToList() is { Count: > 0 } fields)
{
user.UserProfile.Fields =
fields.Select(p => new UserProfile.Field { Name = p.Name!, Value = p.Value!, IsVerified = false })
fields.Select(p => new UserProfile.Field
{
Name = p.Name!,
Value = p.Value!,
IsVerified = false
})
.ToArray();
}
@ -87,7 +92,9 @@ public class AccountController(
{
var rq = new DriveFileCreationRequest
{
Filename = request.Avatar.FileName, IsSensitive = false, MimeType = request.Avatar.ContentType
Filename = request.Avatar.FileName,
IsSensitive = false,
MimeType = request.Avatar.ContentType
};
var avatar = await driveSvc.StoreFile(request.Avatar.OpenReadStream(), user, rq);
user.Avatar = avatar;
@ -99,7 +106,9 @@ public class AccountController(
{
var rq = new DriveFileCreationRequest
{
Filename = request.Banner.FileName, IsSensitive = false, MimeType = request.Banner.ContentType
Filename = request.Banner.FileName,
IsSensitive = false,
MimeType = request.Banner.ContentType
};
var banner = await driveSvc.StoreFile(request.Banner.OpenReadStream(), user, rq);
user.Banner = banner;

View file

@ -145,9 +145,9 @@ public class ConversationsController(
private class Conversation
{
public required string Id { get; init; }
public required Note LastNote;
public required List<User> Users;
public required bool Unread;
public required List<User> Users;
public required string Id { get; init; }
}
}

View file

@ -34,7 +34,12 @@ public class ListController(DatabaseContext db, UserRenderer userRenderer) : Con
var res = await db.UserLists
.Where(p => p.User == user)
.Select(p => new ListEntity { Id = p.Id, Title = p.Name, Exclusive = p.HideFromHomeTl })
.Select(p => new ListEntity
{
Id = p.Id,
Title = p.Name,
Exclusive = p.HideFromHomeTl
})
.ToListAsync();
return Ok(res);
@ -50,7 +55,12 @@ public class ListController(DatabaseContext db, UserRenderer userRenderer) : Con
var res = await db.UserLists
.Where(p => p.User == user && p.Id == id)
.Select(p => new ListEntity { Id = p.Id, Title = p.Name, Exclusive = p.HideFromHomeTl })
.Select(p => new ListEntity
{
Id = p.Id,
Title = p.Name,
Exclusive = p.HideFromHomeTl
})
.FirstOrDefaultAsync() ??
throw GracefulException.RecordNotFound();
@ -79,7 +89,12 @@ public class ListController(DatabaseContext db, UserRenderer userRenderer) : Con
await db.AddAsync(list);
await db.SaveChangesAsync();
var res = new ListEntity { Id = list.Id, Title = list.Name, Exclusive = list.HideFromHomeTl };
var res = new ListEntity
{
Id = list.Id,
Title = list.Name,
Exclusive = list.HideFromHomeTl
};
return Ok(res);
}
@ -105,7 +120,12 @@ public class ListController(DatabaseContext db, UserRenderer userRenderer) : Con
db.Update(list);
await db.SaveChangesAsync();
var res = new ListEntity { Id = list.Id, Title = list.Name, Exclusive = list.HideFromHomeTl };
var res = new ListEntity
{
Id = list.Id,
Title = list.Name,
Exclusive = list.HideFromHomeTl
};
return Ok(res);
}

View file

@ -165,7 +165,7 @@ public class PushController(DatabaseContext db, MetaService meta) : ControllerBa
Status = sub.Types.Contains("status"),
Update = sub.Types.Contains("update"),
FollowRequest = sub.Types.Contains("follow_request")
},
}
};
}
}

View file

@ -273,15 +273,15 @@ public class NoteRenderer(
public class NoteRendererDto
{
public List<AccountEntity>? Accounts;
public List<MentionEntity>? Mentions;
public List<AttachmentEntity>? Attachments;
public List<PollEntity>? Polls;
public List<string>? LikedNotes;
public List<string>? BookmarkedNotes;
public List<string>? PinnedNotes;
public List<string>? Renotes;
public List<EmojiEntity>? Emoji;
public List<string>? LikedNotes;
public List<MentionEntity>? Mentions;
public List<string>? PinnedNotes;
public List<PollEntity>? Polls;
public List<ReactionEntity>? Reactions;
public List<string>? Renotes;
public bool Source;
}

View file

@ -4,6 +4,10 @@ namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
public class AnnouncementEntity
{
[J("reactions")] public List<object> Reactions = []; //FIXME
[J("statuses")] public List<object> Statuses = []; //FIXME
[J("tags")] public List<object> Tags = []; //FIXME
[J("id")] public required string Id { get; set; }
[J("content")] public required string Content { get; set; }
[J("published_at")] public required string PublishedAt { get; set; }
@ -12,10 +16,6 @@ public class AnnouncementEntity
[J("mentions")] public required List<MentionEntity> Mentions { get; set; }
[J("emojis")] public required List<EmojiEntity> Emoji { get; set; }
[J("statuses")] public List<object> Statuses = []; //FIXME
[J("reactions")] public List<object> Reactions = []; //FIXME
[J("tags")] public List<object> Tags = []; //FIXME
[J("published")] public bool Published => true;
[J("all_day")] public bool AllDay => false;
}

View file

@ -5,8 +5,8 @@ namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
public class ConversationEntity : IEntity
{
[J("id")] public required string Id { get; set; }
[J("unread")] public required bool Unread { get; set; }
[J("accounts")] public required List<AccountEntity> Accounts { get; set; }
[J("last_status")] public required StatusEntity LastStatus { get; set; }
[J("id")] public required string Id { get; set; }
}

View file

@ -5,7 +5,6 @@ namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
public class PollEntity : IEntity
{
[J("id")] public required string Id { get; set; }
[J("expires_at")] public required string? ExpiresAt { get; set; }
[J("expired")] public required bool Expired { get; set; }
[J("multiple")] public required bool Multiple { get; set; }
@ -16,6 +15,7 @@ public class PollEntity : IEntity
[J("options")] public required List<PollOptionEntity> Options { get; set; }
[J("emojis")] public List<EmojiEntity> Emoji => []; //TODO
[J("id")] public required string Id { get; set; }
}
public class PollOptionEntity

View file

@ -9,7 +9,6 @@ namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
public class StatusEntity : IEntity
{
[J("id")] public required string Id { get; set; }
[J("content")] public required string? Content { get; set; }
[J("uri")] public required string Uri { get; set; }
[J("url")] public required string Url { get; set; }
@ -53,6 +52,7 @@ public class StatusEntity : IEntity
[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)
{

View file

@ -209,7 +209,7 @@ public class SearchController(
{
Name = p.Name,
Url = $"https://{config.Value.WebDomain}/tags/{p.Name}",
Following = false, //TODO
Following = false //TODO
})
.ToListAsync();
}

View file

@ -324,7 +324,7 @@ public class StatusController(
{
Choices = request.Poll.Options,
Multiple = request.Poll.Multiple,
ExpiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(request.Poll.ExpiresIn),
ExpiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(request.Poll.ExpiresIn)
}
: null;
@ -465,7 +465,12 @@ public class StatusController(
{
var user = HttpContext.GetUserOrFail();
var res = await db.Notes.Where(p => p.Id == id && p.User == user)
.Select(p => new StatusSource { Id = p.Id, ContentWarning = p.Cw ?? "", Text = p.Text ?? "" })
.Select(p => new StatusSource
{
Id = p.Id,
ContentWarning = p.Cw ?? "",
Text = p.Text ?? ""
})
.FirstOrDefaultAsync() ??
throw GracefulException.RecordNotFound();

View file

@ -12,13 +12,13 @@ public class PublicChannel(
bool onlyMedia
) : IChannel
{
public readonly ILogger<PublicChannel> Logger =
connection.ScopeFactory.CreateScope().ServiceProvider.GetRequiredService<ILogger<PublicChannel>>();
public string Name => name;
public List<string> Scopes => ["read:statuses"];
public bool IsSubscribed { get; private set; }
public readonly ILogger<PublicChannel> Logger =
connection.ScopeFactory.CreateScope().ServiceProvider.GetRequiredService<ILogger<PublicChannel>>();
public Task Subscribe(StreamingRequestMessage _)
{
if (IsSubscribed) return Task.CompletedTask;
@ -65,7 +65,9 @@ public class PublicChannel(
var rendered = await renderer.RenderAsync(note, connection.Token.User);
var message = new StreamingUpdateMessage
{
Stream = [Name], Event = "update", Payload = JsonSerializer.Serialize(rendered)
Stream = [Name],
Event = "update",
Payload = JsonSerializer.Serialize(rendered)
};
await connection.SendMessageAsync(JsonSerializer.Serialize(message));
}
@ -85,7 +87,9 @@ public class PublicChannel(
var rendered = await renderer.RenderAsync(note, connection.Token.User);
var message = new StreamingUpdateMessage
{
Stream = [Name], Event = "status.update", Payload = JsonSerializer.Serialize(rendered)
Stream = [Name],
Event = "status.update",
Payload = JsonSerializer.Serialize(rendered)
};
await connection.SendMessageAsync(JsonSerializer.Serialize(message));
}
@ -100,7 +104,12 @@ public class PublicChannel(
try
{
if (!IsApplicable(note)) return;
var message = new StreamingUpdateMessage { Stream = [Name], Event = "delete", Payload = note.Id };
var message = new StreamingUpdateMessage
{
Stream = [Name],
Event = "delete",
Payload = note.Id
};
await connection.SendMessageAsync(JsonSerializer.Serialize(message));
}
catch (Exception e)

View file

@ -10,14 +10,14 @@ namespace Iceshrimp.Backend.Controllers.Mastodon.Streaming.Channels;
public class UserChannel(WebSocketConnection connection, bool notificationsOnly) : IChannel
{
public readonly ILogger<UserChannel> Logger =
connection.ScopeFactory.CreateScope().ServiceProvider.GetRequiredService<ILogger<UserChannel>>();
private List<string> _followedUsers = [];
public string Name => notificationsOnly ? "user:notification" : "user";
public List<string> Scopes => ["read:statuses", "read:notifications"];
public bool IsSubscribed { get; private set; }
public readonly ILogger<UserChannel> Logger =
connection.ScopeFactory.CreateScope().ServiceProvider.GetRequiredService<ILogger<UserChannel>>();
public async Task Subscribe(StreamingRequestMessage _)
{
if (IsSubscribed) return;
@ -74,7 +74,9 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly)
var rendered = await renderer.RenderAsync(note, connection.Token.User);
var message = new StreamingUpdateMessage
{
Stream = [Name], Event = "update", Payload = JsonSerializer.Serialize(rendered)
Stream = [Name],
Event = "update",
Payload = JsonSerializer.Serialize(rendered)
};
await connection.SendMessageAsync(JsonSerializer.Serialize(message));
}
@ -94,7 +96,9 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly)
var rendered = await renderer.RenderAsync(note, connection.Token.User);
var message = new StreamingUpdateMessage
{
Stream = [Name], Event = "status.update", Payload = JsonSerializer.Serialize(rendered)
Stream = [Name],
Event = "status.update",
Payload = JsonSerializer.Serialize(rendered)
};
await connection.SendMessageAsync(JsonSerializer.Serialize(message));
}
@ -109,7 +113,12 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly)
try
{
if (!IsApplicable(note)) return;
var message = new StreamingUpdateMessage { Stream = [Name], Event = "delete", Payload = note.Id };
var message = new StreamingUpdateMessage
{
Stream = [Name],
Event = "delete",
Payload = note.Id
};
await connection.SendMessageAsync(JsonSerializer.Serialize(message));
}
catch (Exception e)
@ -139,7 +148,9 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly)
var message = new StreamingUpdateMessage
{
Stream = [Name], Event = "notification", Payload = JsonSerializer.Serialize(rendered)
Stream = [Name],
Event = "notification",
Payload = JsonSerializer.Serialize(rendered)
};
await connection.SendMessageAsync(JsonSerializer.Serialize(message));
}

View file

@ -3,10 +3,8 @@ using Iceshrimp.Backend.Controllers.Attributes;
using Iceshrimp.Backend.Controllers.Renderers;
using Iceshrimp.Backend.Controllers.Schemas;
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 Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore;
@ -14,7 +12,8 @@ using Microsoft.EntityFrameworkCore;
namespace Iceshrimp.Backend.Controllers;
[ApiController]
[Authenticate, Authorize]
[Authenticate]
[Authorize]
[EnableRateLimiting("sliding")]
[Route("/api/iceshrimp/v1/notification")]
[Produces(MediaTypeNames.Application.Json)]

View file

@ -73,7 +73,7 @@ public class NoteRenderer(UserRenderer userRenderer, DatabaseContext db, EmojiSe
i.Reaction == p.First().Reaction &&
i.User == user),
Name = p.First().Reaction,
Url = null,
Url = null
})
.ToListAsync();
@ -102,8 +102,8 @@ public class NoteRenderer(UserRenderer userRenderer, DatabaseContext db, EmojiSe
public class NoteRendererDto
{
public List<UserResponse>? Users;
public List<NoteAttachment>? Attachments;
public List<NoteReactionSchema>? Reactions;
public List<UserResponse>? Users;
}
}

View file

@ -1,9 +1,6 @@
using Iceshrimp.Backend.Controllers.Schemas;
using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Services;
using Microsoft.EntityFrameworkCore;
namespace Iceshrimp.Backend.Controllers.Renderers;
@ -81,7 +78,7 @@ public class NotificationRenderer(
public class NotificationRendererDto
{
public List<UserResponse>? Users;
public List<NoteResponse>? Notes;
public List<UserResponse>? Users;
}
}

View file

@ -36,7 +36,7 @@ public class UserProfileRenderer(DatabaseContext db)
Fields = user.UserProfile?.Fields,
Location = user.UserProfile?.Location,
Followers = followers,
Following = following,
Following = following
};
}

View file

@ -1,5 +1,4 @@
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
using JI = System.Text.Json.Serialization.JsonIgnoreAttribute;
namespace Iceshrimp.Backend.Controllers.Schemas;

View file

@ -84,8 +84,8 @@ public class DatabaseContext(DbContextOptions<DatabaseContext> options)
public virtual DbSet<Webhook> Webhooks { get; init; } = null!;
public virtual DbSet<AllowedInstance> AllowedInstances { get; init; } = null!;
public virtual DbSet<BlockedInstance> BlockedInstances { get; init; } = null!;
public virtual DbSet<DataProtectionKey> DataProtectionKeys { get; init; } = null!;
public virtual DbSet<MetaStoreEntry> MetaStore { get; init; } = null!;
public virtual DbSet<DataProtectionKey> DataProtectionKeys { get; init; } = null!;
public static NpgsqlDataSource GetDataSource(Config.DatabaseSection? config)
{

View file

@ -184,14 +184,14 @@ public class DriveFile : IEntity
[InverseProperty(nameof(Tables.User.Banner))]
public virtual User? UserBanner { get; set; }
[NotMapped] public string PublicUrl => WebpublicUrl ?? Url;
[NotMapped] public string PublicThumbnailUrl => ThumbnailUrl ?? WebpublicUrl ?? Url;
[Key]
[Column("id")]
[StringLength(32)]
public string Id { get; set; } = null!;
[NotMapped] public string PublicUrl => WebpublicUrl ?? Url;
[NotMapped] public string PublicThumbnailUrl => ThumbnailUrl ?? WebpublicUrl ?? Url;
public class FileProperties
{
[J("width")] public int? Width { get; set; }

View file

@ -8,10 +8,10 @@ namespace Iceshrimp.Backend.Core.Database.Tables;
[Index("Name", IsUnique = true)]
public class Hashtag : IEntity
{
[Column("name")] [StringLength(128)] public string Name { get; set; } = null!;
[Key]
[Column("id")]
[StringLength(32)]
public string Id { get; set; } = null!;
[Column("name")] [StringLength(128)] public string Name { get; set; } = null!;
}

View file

@ -11,13 +11,16 @@ namespace Iceshrimp.Backend.Core.Database.Tables;
[PrimaryKey("UserId", "Type")]
public class Marker
{
[Column("userId")]
[StringLength(32)]
public string UserId { get; set; } = null!;
[PgName("marker_type_enum")]
public enum MarkerType
{
[PgName("home")] Home,
[PgName("notifications")] Notifications
}
[Column("type")]
[StringLength(32)]
public MarkerType Type { get; set; }
[Column("userId")] [StringLength(32)] public string UserId { get; set; } = null!;
[Column("type")] [StringLength(32)] public MarkerType Type { get; set; }
[Column("position")]
[StringLength(32)]
@ -30,11 +33,4 @@ public class Marker
[ForeignKey("UserId")]
[InverseProperty(nameof(Tables.User.Markers))]
public virtual User User { get; set; } = null!;
[PgName("marker_type_enum")]
public enum MarkerType
{
[PgName("home")] Home,
[PgName("notifications")] Notifications
}
}

View file

@ -241,9 +241,6 @@ public class Note : IEntity
public string RawAttachments
=> InternalRawAttachments(Id);
public static string InternalRawAttachments(string id)
=> throw new NotSupportedException();
[NotMapped] [Projectable] public bool IsPureRenote => (RenoteId != null || Renote != null) && !IsQuote;
[NotMapped]
@ -265,6 +262,9 @@ public class Note : IEntity
[StringLength(32)]
public string Id { get; set; } = null!;
public static string InternalRawAttachments(string id)
=> throw new NotSupportedException();
[Projectable]
public bool TextContainsCaseInsensitive(string str) =>
Text != null && EF.Functions.ILike(Text, "%" + EfHelpers.EscapeLikeQuery(str) + "%", @"\");

View file

@ -117,15 +117,15 @@ public class Notification : IEntity
[InverseProperty(nameof(Tables.UserGroupInvitation.Notifications))]
public virtual UserGroupInvitation? UserGroupInvitation { get; set; }
[Column("masto_id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long MastoId { get; set; }
[Key]
[Column("id")]
[StringLength(32)]
public string Id { get; set; } = null!;
[Column("masto_id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long MastoId { get; set; }
public Notification WithPrecomputedNoteVisibilities(bool reply, bool renote)
{
Note = Note?.WithPrecomputedVisibilities(reply, renote);

View file

@ -16,9 +16,12 @@ public class PushSubscription
[PgName("all")] All,
[PgName("followed")] Followed,
[PgName("follower")] Follower,
[PgName("none")] None,
[PgName("none")] None
}
[Column("types", TypeName = "character varying(32)[]")]
public List<string> Types = null!;
[Key]
[Column("id")]
[StringLength(32)]
@ -42,9 +45,6 @@ public class PushSubscription
[StringLength(128)]
public string PublicKey { get; set; } = null!;
[Column("types", TypeName = "character varying(32)[]")]
public List<string> Types = null!;
[Column("policy")] public PushPolicy Policy { get; set; }
[ForeignKey("UserId")]

View file

@ -494,6 +494,9 @@ public class User : IEntity
[NotMapped] public bool? PrecomputedIsRequested { get; set; }
[NotMapped] public bool? PrecomputedIsRequestedBy { get; set; }
public bool IsLocalUser => Host == null;
public bool IsRemoteUser => Host != null;
[Key]
[Column("id")]
[StringLength(32)]
@ -616,7 +619,4 @@ public class User : IEntity
: throw new Exception("Cannot access PublicUrl for remote user");
public string GetIdenticonUrl(string webDomain) => $"https://{webDomain}/identicon/{Id}";
public bool IsLocalUser => Host == null;
public bool IsRemoteUser => Host != null;
}

View file

@ -20,6 +20,8 @@ public class UserProfile
[PgName("private")] Private
}
[Column("mentionsResolved")] public bool MentionsResolved;
[Key]
[Column("userId")]
[StringLength(32)]
@ -176,9 +178,6 @@ public class UserProfile
[InverseProperty(nameof(Tables.User.UserProfile))]
public virtual User User { get; set; } = null!;
[Column("mentionsResolved")]
public bool MentionsResolved;
public class Field
{
[J("name")] public required string Name { get; set; }

View file

@ -39,17 +39,16 @@ public static class MvcBuilderExtensions
public static IMvcBuilder AddModelBindingProviders(this IMvcBuilder builder)
{
builder.Services.AddOptions<MvcOptions>().PostConfigure(options =>
{
options.ModelBinderProviders.AddHybridBindingProvider();
});
builder.Services.AddOptions<MvcOptions>()
.PostConfigure(options => { options.ModelBinderProviders.AddHybridBindingProvider(); });
return builder;
}
public static IMvcBuilder AddValueProviderFactories(this IMvcBuilder builder)
{
builder.Services.AddOptions<MvcOptions>().PostConfigure(options =>
builder.Services.AddOptions<MvcOptions>()
.PostConfigure(options =>
{
options.ValueProviderFactories.Add(new JQueryQueryStringValueProviderFactory());
});

View file

@ -289,7 +289,9 @@ public static class QueryableExtensions
.Where(note => note.Reply == null || !note.Reply.User.IsMuting(user));
}
public static IQueryable<Note> FilterBlockedConversations(this IQueryable<Note> query, User user, DatabaseContext db)
public static IQueryable<Note> FilterBlockedConversations(
this IQueryable<Note> query, User user, DatabaseContext db
)
{
return query.Where(p => !db.Blockings.Any(i => i.Blocker == user && p.VisibleUserIds.Contains(i.BlockeeId)));
}

View file

@ -79,7 +79,9 @@ public class UserRenderer(IOptions<Config.InstanceSection> config, DatabaseConte
Endpoints = new ASEndpoints { SharedInbox = new ASObjectBase($"https://{config.Value.WebDomain}/inbox") },
PublicKey = new ASPublicKey
{
Id = $"{id}#main-key", Owner = new ASObjectBase(id), PublicKey = keypair.PublicKey
Id = $"{id}#main-key",
Owner = new ASObjectBase(id),
PublicKey = keypair.PublicKey
},
Tags = tags
};

View file

@ -64,8 +64,6 @@ public static class LdHelpers
JToken.Parse(File.ReadAllText(Path.Combine("Core", "Federation", "ActivityStreams", "Contexts",
"as-extensions.json")));
private static IEnumerable<string> ASForceArray => ["tag", "to", "cc", "bcc", "bto"];
private static readonly JsonLdProcessorOptions Options = new()
{
DocumentLoader = CustomLoader,
@ -75,7 +73,7 @@ public static class LdHelpers
ForceArray = ASForceArray.Select(p => $"{Constants.ActivityStreamsNs}#{p}").ToList(),
// separated for readability
RemoveUnusedInlineContextProperties = true,
RemoveUnusedInlineContextProperties = true
};
public static readonly JsonSerializerSettings JsonSerializerSettings = new()
@ -88,6 +86,8 @@ public static class LdHelpers
NullValueHandling = NullValueHandling.Ignore, DateTimeZoneHandling = DateTimeZoneHandling.Local
};
private static IEnumerable<string> ASForceArray => ["tag", "to", "cc", "bcc", "bto"];
private static RemoteDocument CustomLoader(Uri uri, JsonLdLoaderOptions jsonLdLoaderOptions)
{
var key = uri.AbsolutePath == "/schemas/litepub-0.1.jsonld" ? "litepub-0.1" : uri.ToString();

View file

@ -37,7 +37,7 @@ public class ASActivity : ASObject
// Extensions
public const string Bite = "https://ns.mia.jetzt/as#Bite";
public const string EmojiReact = $"http://litepub.social/ns#EmojiReact";
public const string EmojiReact = "http://litepub.social/ns#EmojiReact";
}
}

View file

@ -36,13 +36,13 @@ public class ASDocument : ASAttachment
public class ASField : ASAttachment
{
public ASField() => Type = $"{Constants.SchemaNs}#PropertyValue";
[J($"{Constants.ActivityStreamsNs}#name")] [JC(typeof(ValueObjectConverter))]
public string? Name;
[J($"{Constants.SchemaNs}#value")] [JC(typeof(ValueObjectConverter))]
public string? Value;
public ASField() => Type = $"{Constants.SchemaNs}#PropertyValue";
}
public sealed class ASAttachmentConverter : JsonConverter

View file

@ -10,6 +10,8 @@ namespace Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
public class ASCollection : ASObject
{
public const string ObjectType = $"{Constants.ActivityStreamsNs}#Collection";
[JsonConstructor]
public ASCollection(bool withType = true) => Type = withType ? ObjectType : null;
@ -37,8 +39,6 @@ public class ASCollection : ASObject
public ASLink? Last { get; set; }
public new bool IsUnresolved => !TotalItems.HasValue;
public const string ObjectType = $"{Constants.ActivityStreamsNs}#Collection";
}
public sealed class ASCollectionConverter : JsonConverter

View file

@ -7,6 +7,8 @@ namespace Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
public class ASCollectionBase : ASObjectBase
{
public const string ObjectType = $"{Constants.ActivityStreamsNs}#Collection";
[J("@type")]
[JC(typeof(StringListSingleConverter))]
public string Type => ObjectType;
@ -14,8 +16,6 @@ public class ASCollectionBase : ASObjectBase
[J($"{Constants.ActivityStreamsNs}#totalItems")]
[JC(typeof(VC))]
public ulong? TotalItems { get; set; }
public const string ObjectType = $"{Constants.ActivityStreamsNs}#Collection";
}
public sealed class ASCollectionBaseConverter : ASSerializer.ListSingleObjectConverter<ASCollectionBase>;

View file

@ -10,6 +10,8 @@ namespace Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
public class ASCollectionPage : ASObject
{
public const string ObjectType = $"{Constants.ActivityStreamsNs}#CollectionPage";
[JsonConstructor]
public ASCollectionPage(bool withType = true) => Type = withType ? ObjectType : null;
@ -35,8 +37,6 @@ public class ASCollectionPage : ASObject
[J($"{Constants.ActivityStreamsNs}#next")]
[JC(typeof(ASLinkConverter))]
public ASLink? Next { get; set; }
public const string ObjectType = $"{Constants.ActivityStreamsNs}#CollectionPage";
}
public sealed class ASCollectionPageConverter : JsonConverter

View file

@ -9,6 +9,8 @@ namespace Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
public class ASOrderedCollection : ASCollection
{
public new const string ObjectType = $"{Constants.ActivityStreamsNs}#OrderedCollection";
[JsonConstructor]
public ASOrderedCollection(bool withType = true) => Type = withType ? ObjectType : null;
@ -22,8 +24,6 @@ public class ASOrderedCollection : ASCollection
get => base.Items;
set => base.Items = value;
}
public new const string ObjectType = $"{Constants.ActivityStreamsNs}#OrderedCollection";
}
internal sealed class ASOrderedCollectionItemsConverter : ASCollectionItemsConverter

View file

@ -10,6 +10,8 @@ namespace Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
public class ASOrderedCollectionPage : ASObject
{
public const string ObjectType = $"{Constants.ActivityStreamsNs}#OrderedCollectionPage";
[JsonConstructor]
public ASOrderedCollectionPage(bool withType = true) => Type = withType ? ObjectType : null;
@ -35,8 +37,6 @@ public class ASOrderedCollectionPage : ASObject
[J($"{Constants.ActivityStreamsNs}#next")]
[JC(typeof(ASLinkConverter))]
public ASLink? Next { get; set; }
public const string ObjectType = $"{Constants.ActivityStreamsNs}#OrderedCollectionPage";
}
public sealed class ASOrderedCollectionPageConverter : JsonConverter

View file

@ -7,10 +7,9 @@ namespace Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
public class ASQuestion : ASNote
{
public ASQuestion() => Type = Types.Question;
private List<ASQuestionOption>? _anyOf;
private List<ASQuestionOption>? _oneOf;
public ASQuestion() => Type = Types.Question;
[J($"{Constants.ActivityStreamsNs}#oneOf")]
public List<ASQuestionOption>? OneOf

View file

@ -306,7 +306,9 @@ internal class MentionNodeParser : INodeParser
var node = new MfmMentionNode
{
Username = split[0], Host = split.Length == 2 ? split[1] : null, Acct = $"@{buffer[start..end]}"
Username = split[0],
Host = split.Length == 2 ? split[1] : null,
Acct = $"@{buffer[start..end]}"
};
return (node, chars);

View file

@ -146,11 +146,11 @@ public abstract class BackgroundTaskQueue
)
{
var db = scope.GetRequiredService<DatabaseContext>();
var poll = await db.Polls.FirstOrDefaultAsync(p => p.NoteId == job.NoteId, cancellationToken: token);
var poll = await db.Polls.FirstOrDefaultAsync(p => p.NoteId == job.NoteId, token);
if (poll == null) return;
if (poll.ExpiresAt > DateTime.UtcNow + TimeSpan.FromSeconds(30)) return;
var note = await db.Notes.IncludeCommonProperties()
.FirstOrDefaultAsync(p => p.Id == poll.NoteId, cancellationToken: token);
.FirstOrDefaultAsync(p => p.Id == poll.NoteId, token);
if (note == null) return;
var notificationSvc = scope.GetRequiredService<NotificationService>();
@ -159,7 +159,7 @@ public abstract class BackgroundTaskQueue
{
var voters = await db.PollVotes.Where(p => p.Note == note && p.User.Host != null)
.Select(p => p.User)
.ToListAsync(cancellationToken: token);
.ToListAsync(token);
if (voters.Count == 0) return;

View file

@ -30,7 +30,7 @@ public class InstanceService(DatabaseContext db, HttpClient httpClient)
Id = IdHelpers.GenerateSlowflakeId(),
Host = host,
CaughtAt = DateTime.UtcNow,
LastCommunicatedAt = DateTime.UtcNow,
LastCommunicatedAt = DateTime.UtcNow
};
await db.AddAsync(instance);
await db.SaveChangesAsync();

View file

@ -76,7 +76,12 @@ public class SystemUserService(ILogger<SystemUserService> logger, DatabaseContex
PublicKey = keypair.ExportSubjectPublicKeyInfoPem()
};
var userProfile = new UserProfile { UserId = user.Id, AutoAcceptFollowed = false, Password = null };
var userProfile = new UserProfile
{
UserId = user.Id,
AutoAcceptFollowed = false,
Password = null
};
var usedUsername = new UsedUsername { CreatedAt = DateTime.UtcNow, Username = username.ToLowerInvariant() };

View file

@ -298,8 +298,8 @@ public class UserService(
db.Update(user);
await db.SaveChangesAsync();
await processPendingDeletes();
user = await UpdateProfileMentions(user, actor, force: true);
UpdateUserPinnedNotesInBackground(actor, user, force: true);
user = await UpdateProfileMentions(user, actor, true);
UpdateUserPinnedNotesInBackground(actor, user, true);
return user;
}

View file

@ -16,53 +16,53 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Amazon.S3" Version="0.31.2" />
<PackageReference Include="AngleSharp" Version="1.1.0" />
<PackageReference Include="Asp.Versioning.Http" Version="8.0.0" />
<PackageReference Include="AsyncKeyedLock" Version="6.3.4" />
<PackageReference Include="Blurhash.ImageSharp" Version="3.0.0" />
<PackageReference Include="cuid.net" Version="5.0.2" />
<PackageReference Include="dotNetRdf.Core" Version="3.2.6-iceshrimp" />
<PackageReference Include="EntityFrameworkCore.Exceptions.PostgreSQL" Version="8.0.0" />
<PackageReference Include="EntityFrameworkCore.Projectables" Version="3.0.4" />
<PackageReference Include="Isopoh.Cryptography.Argon2" Version="2.0.0" />
<PackageReference Include="libsodium" Version="1.0.18.4" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="8.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" />
<PackageReference Include="Amazon.S3" Version="0.31.2"/>
<PackageReference Include="AngleSharp" Version="1.1.0"/>
<PackageReference Include="Asp.Versioning.Http" Version="8.0.0"/>
<PackageReference Include="AsyncKeyedLock" Version="6.3.4"/>
<PackageReference Include="Blurhash.ImageSharp" Version="3.0.0"/>
<PackageReference Include="cuid.net" Version="5.0.2"/>
<PackageReference Include="dotNetRdf.Core" Version="3.2.6-iceshrimp"/>
<PackageReference Include="EntityFrameworkCore.Exceptions.PostgreSQL" Version="8.0.0"/>
<PackageReference Include="EntityFrameworkCore.Projectables" Version="3.0.4"/>
<PackageReference Include="Isopoh.Cryptography.Argon2" Version="2.0.0"/>
<PackageReference Include="libsodium" Version="1.0.18.4"/>
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="8.0.1"/>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.0"/>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="8.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
<PackageReference Include="protobuf-net" Version="3.2.30" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.1" />
<PackageReference Include="StackExchange.Redis" Version="2.7.17" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="System.IO.Hashing" Version="8.0.0" />
<PackageReference Include="Vite.AspNetCore" Version="1.11.0" />
<PackageReference Include="WebPush" Version="1.0.24-iceshrimp" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.1"/>
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="8.0.0"/>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0"/>
<PackageReference Include="protobuf-net" Version="3.2.30"/>
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.1"/>
<PackageReference Include="StackExchange.Redis" Version="2.7.17"/>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0"/>
<PackageReference Include="System.IO.Hashing" Version="8.0.0"/>
<PackageReference Include="Vite.AspNetCore" Version="1.11.0"/>
<PackageReference Include="WebPush" Version="1.0.24-iceshrimp"/>
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="Pages\Error.cshtml" />
<AdditionalFiles Include="Pages\Shared\_Layout.cshtml" />
<AdditionalFiles Include="Pages\_ViewImports.cshtml" />
<AdditionalFiles Include="Pages\_ViewStart.cshtml" />
<AdditionalFiles Include="Pages\Error.cshtml"/>
<AdditionalFiles Include="Pages\Shared\_Layout.cshtml"/>
<AdditionalFiles Include="Pages\_ViewImports.cshtml"/>
<AdditionalFiles Include="Pages\_ViewStart.cshtml"/>
</ItemGroup>
<ItemGroup>
<Content Include="wwwroot\.vite\manifest.json" CopyToPublishDirectory="PreserveNewest" />
<Content Include="wwwroot\.vite\manifest.json" CopyToPublishDirectory="PreserveNewest"/>
</ItemGroup>
<ItemGroup>
<None Remove="migrate.sql" />
<None Remove="migrate.sql"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Iceshrimp.Parsing\Iceshrimp.Parsing.fsproj" />
<ProjectReference Include="..\Iceshrimp.Parsing\Iceshrimp.Parsing.fsproj"/>
</ItemGroup>
</Project>

View file

@ -6,11 +6,11 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="SearchQuery.fs" />
<Compile Include="SearchQuery.fs"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="FParsec" Version="1.1.1" />
<PackageReference Include="FParsec" Version="1.1.1"/>
</ItemGroup>
</Project>

View file

@ -16,7 +16,8 @@ public class SearchQueryTests
}
[TestMethod]
[DataRow(false), DataRow(true)]
[DataRow(false)]
[DataRow(true)]
public void TestParseFrom(bool negated)
{
List<string> candidates = ["from", "author", "by", "user"];
@ -27,7 +28,8 @@ public class SearchQueryTests
}
[TestMethod]
[DataRow(false), DataRow(true)]
[DataRow(false)]
[DataRow(true)]
public void TestParseMention(bool negated)
{
List<string> candidates = ["mention", "mentions", "mentioning"];
@ -38,7 +40,8 @@ public class SearchQueryTests
}
[TestMethod]
[DataRow(false), DataRow(true)]
[DataRow(false)]
[DataRow(true)]
public void TestParseReply(bool negated)
{
List<string> candidates = ["reply", "replying", "to"];
@ -49,7 +52,8 @@ public class SearchQueryTests
}
[TestMethod]
[DataRow(false), DataRow(true)]
[DataRow(false)]
[DataRow(true)]
public void TestParseInstance(bool negated)
{
List<string> candidates = ["instance", "domain", "host"];
@ -78,7 +82,8 @@ public class SearchQueryTests
}
[TestMethod]
[DataRow(false), DataRow(true)]
[DataRow(false)]
[DataRow(true)]
public void TestParseAttachment(bool negated)
{
List<string> keyCandidates = ["has", "attachment", "attached"];
@ -127,7 +132,8 @@ public class SearchQueryTests
}
[TestMethod]
[DataRow(false), DataRow(true)]
[DataRow(false)]
[DataRow(true)]
public void TestParseIn(bool negated)
{
var key = negated ? "-in" : "in";
@ -147,7 +153,8 @@ public class SearchQueryTests
}
[TestMethod]
[DataRow(false), DataRow(true)]
[DataRow(false)]
[DataRow(true)]
public void TestParseMisc(bool negated)
{
var key = negated ? "-filter" : "filter";
@ -165,7 +172,7 @@ public class SearchQueryTests
new MiscFilter(negated, "renotes"),
new MiscFilter(negated, "renotes"),
new MiscFilter(negated, "renotes"),
new MiscFilter(negated, "renotes"),
new MiscFilter(negated, "renotes")
];
results.Should()
.HaveCount(expectedResults.Count)
@ -173,7 +180,8 @@ public class SearchQueryTests
}
[TestMethod]
[DataRow(false), DataRow(true)]
[DataRow(false)]
[DataRow(true)]
public void TestParseWord(bool negated)
{
List<string> candidates = ["test", "word", "since:2023-10-10invalid", "in:bookmarkstypo"];