[backend] Apply formatting rules

This commit is contained in:
Laura Hausmann 2024-06-29 01:06:19 +02:00
parent 571f2274f2
commit df3a7bdfe5
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
82 changed files with 471 additions and 462 deletions

View file

@ -3,7 +3,6 @@ using System.Net.Mime;
using Iceshrimp.Backend.Controllers.Attributes;
using Iceshrimp.Backend.Controllers.Federation;
using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Shared.Schemas;
using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
@ -12,6 +11,7 @@ using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
using Iceshrimp.Backend.Core.Helpers;
using Iceshrimp.Backend.Core.Middleware;
using Iceshrimp.Backend.Core.Services;
using Iceshrimp.Shared.Schemas;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
@ -125,7 +125,7 @@ public class AdminController(
.FirstOrDefaultAsync(p => p.Id == id && p.UserHost == null);
if (note == null) return NotFound();
var rendered = await noteRenderer.RenderAsync(note);
var compacted = LdHelpers.Compact(rendered);
var compacted = rendered.Compact();
return Ok(compacted);
}

View file

@ -1,12 +1,12 @@
using System.Diagnostics.CodeAnalysis;
using System.Net.Mime;
using Iceshrimp.Backend.Controllers.Renderers;
using Iceshrimp.Shared.Schemas;
using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Helpers;
using Iceshrimp.Backend.Core.Middleware;
using Iceshrimp.Backend.Core.Services;
using Iceshrimp.Shared.Schemas;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore;

View file

@ -1,7 +1,7 @@
using System.Net;
using System.Net.Mime;
using Iceshrimp.Shared.Schemas;
using Iceshrimp.Backend.Core.Middleware;
using Iceshrimp.Shared.Schemas;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;

View file

@ -1,7 +1,6 @@
using System.Text;
using Iceshrimp.Backend.Controllers.Attributes;
using Iceshrimp.Backend.Controllers.Federation.Attributes;
using Iceshrimp.Shared.Schemas;
using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Extensions;
@ -10,6 +9,7 @@ using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
using Iceshrimp.Backend.Core.Middleware;
using Iceshrimp.Backend.Core.Queues;
using Iceshrimp.Backend.Core.Services;
using Iceshrimp.Shared.Schemas;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

View file

@ -4,11 +4,11 @@ using System.Text;
using System.Xml.Serialization;
using Iceshrimp.Backend.Controllers.Federation.Attributes;
using Iceshrimp.Backend.Controllers.Federation.Schemas;
using Iceshrimp.Shared.Schemas;
using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Federation.WebFinger;
using Iceshrimp.Shared.Schemas;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

View file

@ -88,7 +88,7 @@ public class MediaController(DriveService driveSvc, DatabaseContext db) : Contro
Description = file.Comment,
PreviewUrl = file.PublicThumbnailUrl,
RemoteUrl = file.Uri,
Sensitive = file.IsSensitive,
Sensitive = file.IsSensitive
//Metadata = TODO,
};
}

View file

@ -20,6 +20,20 @@ public class NoteRenderer(
EmojiService emojiSvc
)
{
private static readonly FilterResultEntity InaccessibleFilter = new()
{
Filter = new FilterEntity
{
Title = "HideInaccessible",
FilterAction = "hide",
Id = "0",
Context = ["home", "thread", "notifications", "account", "public"],
Keywords = [new FilterKeyword("RE: \ud83d\udd12", 0, 0)],
ExpiresAt = null
},
KeywordMatches = ["RE: \ud83d\udd12"] // lock emoji
};
public async Task<StatusEntity> RenderAsync(
Note note, User? user, Filter.FilterContext? filterContext = null, NoteRendererDto? data = null, int recurse = 2
)
@ -150,20 +164,6 @@ public class NoteRenderer(
return res;
}
private static readonly FilterResultEntity InaccessibleFilter = new()
{
Filter = new FilterEntity
{
Title = "HideInaccessible",
FilterAction = "hide",
Id = "0",
Context = ["home", "thread", "notifications", "account", "public"],
Keywords = [new FilterKeyword("RE: \ud83d\udd12", 0, 0)],
ExpiresAt = null
},
KeywordMatches = ["RE: \ud83d\udd12"] // lock emoji
};
public async Task<List<StatusEdit>> RenderHistoryAsync(Note note)
{
var edits = await db.NoteEdits.Where(p => p.Note == note).OrderBy(p => p.Id).ToListAsync();
@ -430,13 +430,13 @@ public class NoteRenderer(
public List<AttachmentEntity>? Attachments;
public List<string>? BookmarkedNotes;
public List<EmojiEntity>? Emoji;
public List<Filter>? Filters;
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 List<Filter>? Filters;
public bool Source;
}

View file

@ -9,7 +9,7 @@ namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
public class StatusEntity : IEntity, ICloneable
{
[J("id")] public required string Id { get; set; }
[JI] public string? MastoReplyUserId;
[J("text")] public required string? Text { get; set; }
[J("content")] public required string? Content { get; set; }
[J("uri")] public required string Uri { get; set; }
@ -52,7 +52,8 @@ public class StatusEntity : IEntity, ICloneable
[J("language")] public string? Language => null; //FIXME
[JI] public string? MastoReplyUserId;
public object Clone() => MemberwiseClone();
[J("id")] public required string Id { get; set; }
public static string EncodeVisibility(Note.NoteVisibility visibility)
{
@ -77,8 +78,6 @@ public class StatusEntity : IEntity, ICloneable
_ => throw GracefulException.BadRequest($"Unknown visibility: {visibility}")
};
}
public object Clone() => MemberwiseClone();
}
public class StatusContext

View file

@ -97,6 +97,8 @@ public abstract class StatusSchemas
public class ReblogRequest
{
[B(Name = "visibility")] [J("visibility")] public string? Visibility { get; set; }
[B(Name = "visibility")]
[J("visibility")]
public string? Visibility { get; set; }
}
}

View file

@ -12,7 +12,6 @@ using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Helpers;
using Iceshrimp.Backend.Core.Helpers.LibMfm.Parsing;
using Iceshrimp.Backend.Core.Helpers.LibMfm.Serialization;
using static Iceshrimp.Parsing.MfmNodeTypes;
using Iceshrimp.Backend.Core.Middleware;
using Iceshrimp.Backend.Core.Services;
using Microsoft.AspNetCore.Cors;
@ -20,6 +19,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using static Iceshrimp.Parsing.MfmNodeTypes;
namespace Iceshrimp.Backend.Controllers.Mastodon;
@ -58,7 +58,7 @@ public class StatusController(
var note = await db.Notes
.Where(p => p.Id == id)
.IncludeCommonProperties()
.FilterHidden(user, db, filterOutgoingBlocks: false, filterMutes: false,
.FilterHidden(user, db, false, false,
filterMentions: false)
.EnsureVisibleFor(user)
.PrecomputeVisibilities(user)
@ -89,7 +89,7 @@ public class StatusController(
var note = await db.Notes
.Where(p => p.Id == id)
.EnsureVisibleFor(user)
.FilterHidden(user, db, filterOutgoingBlocks: false, filterMutes: false)
.FilterHidden(user, db, false, false)
.FirstOrDefaultAsync() ??
throw GracefulException.RecordNotFound();

View file

@ -63,12 +63,6 @@ public class DirectChannel(WebSocketConnection connection) : IChannel
return wrapped;
}
private class NoteWithVisibilities(Note note)
{
public readonly Note Note = note;
public Note? Renote = note.Renote;
}
private static StatusEntity EnforceRenoteReplyVisibility(StatusEntity rendered, NoteWithVisibilities note)
{
var renote = note.Renote == null && rendered.Renote != null;
@ -149,4 +143,10 @@ public class DirectChannel(WebSocketConnection connection) : IChannel
_logger.LogError("Event handler OnNoteUpdated threw exception: {e}", e);
}
}
private class NoteWithVisibilities(Note note)
{
public readonly Note Note = note;
public Note? Renote = note.Renote;
}
}

View file

@ -13,13 +13,13 @@ public class HashtagChannel(WebSocketConnection connection, bool local) : IChann
private readonly ILogger<HashtagChannel> _logger =
connection.Scope.ServiceProvider.GetRequiredService<ILogger<HashtagChannel>>();
private readonly WriteLockingList<string> _tags = [];
public string Name => local ? "hashtag:local" : "hashtag";
public List<string> Scopes => ["read:statuses"];
public bool IsSubscribed => _tags.Count != 0;
public bool IsAggregate => true;
private readonly WriteLockingList<string> _tags = [];
public async Task Subscribe(StreamingRequestMessage msg)
{
if (msg.Tag == null)
@ -79,12 +79,6 @@ public class HashtagChannel(WebSocketConnection connection, bool local) : IChann
return wrapped;
}
private class NoteWithVisibilities(Note note)
{
public readonly Note Note = note;
public Note? Renote = note.Renote;
}
private static StatusEntity EnforceRenoteReplyVisibility(StatusEntity rendered, NoteWithVisibilities note)
{
var renote = note.Renote == null && rendered.Renote != null;
@ -169,4 +163,10 @@ public class HashtagChannel(WebSocketConnection connection, bool local) : IChann
_logger.LogError("Event handler OnNoteDeleted threw exception: {e}", e);
}
}
private class NoteWithVisibilities(Note note)
{
public readonly Note Note = note;
public Note? Renote = note.Renote;
}
}

View file

@ -12,18 +12,19 @@ namespace Iceshrimp.Backend.Controllers.Mastodon.Streaming.Channels;
public class ListChannel(WebSocketConnection connection) : IChannel
{
private readonly WriteLockingList<string> _lists = [];
private readonly ILogger<ListChannel> _logger =
connection.Scope.ServiceProvider.GetRequiredService<ILogger<ListChannel>>();
private readonly ConcurrentDictionary<string, WriteLockingList<string>> _members = [];
private IEnumerable<string> _applicableUserIds = [];
public string Name => "list";
public List<string> Scopes => ["read:statuses"];
public bool IsSubscribed => _lists.Count != 0;
public bool IsAggregate => true;
private readonly WriteLockingList<string> _lists = [];
private readonly ConcurrentDictionary<string, WriteLockingList<string>> _members = [];
private IEnumerable<string> _applicableUserIds = [];
public async Task Subscribe(StreamingRequestMessage msg)
{
if (msg.List == null)
@ -101,12 +102,6 @@ public class ListChannel(WebSocketConnection connection) : IChannel
return wrapped;
}
private class NoteWithVisibilities(Note note)
{
public readonly Note Note = note;
public Note? Renote = note.Renote;
}
private static StatusEntity EnforceRenoteReplyVisibility(StatusEntity rendered, NoteWithVisibilities note)
{
var renote = note.Renote == null && rendered.Renote != null;
@ -218,4 +213,10 @@ public class ListChannel(WebSocketConnection connection) : IChannel
_logger.LogError("Event handler OnListUpdated threw exception: {e}", e);
}
}
private class NoteWithVisibilities(Note note)
{
public readonly Note Note = note;
public Note? Renote = note.Renote;
}
}

View file

@ -71,12 +71,6 @@ public class PublicChannel(
return wrapped;
}
private class NoteWithVisibilities(Note note)
{
public readonly Note Note = note;
public Note? Renote = note.Renote;
}
private static StatusEntity EnforceRenoteReplyVisibility(StatusEntity rendered, NoteWithVisibilities note)
{
var renote = note.Renote == null && rendered.Renote != null;
@ -160,4 +154,10 @@ public class PublicChannel(
_logger.LogError("Event handler OnNoteDeleted threw exception: {e}", e);
}
}
private class NoteWithVisibilities(Note note)
{
public readonly Note Note = note;
public Note? Renote = note.Renote;
}
}

View file

@ -83,12 +83,6 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly)
return wrapped;
}
private class NoteWithVisibilities(Note note)
{
public readonly Note Note = note;
public Note? Renote = note.Renote;
}
private static StatusEntity EnforceRenoteReplyVisibility(StatusEntity rendered, NoteWithVisibilities note)
{
var renote = note.Renote == null && rendered.Renote != null;
@ -206,4 +200,10 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly)
_logger.LogError("Event handler OnNotification threw exception: {e}", e);
}
}
private class NoteWithVisibilities(Note note)
{
public readonly Note Note = note;
public Note? Renote = note.Renote;
}
}

View file

@ -23,18 +23,18 @@ public sealed class WebSocketConnection(
CancellationToken ct
) : IDisposable
{
private readonly WriteLockingList<string> _blockedBy = [];
private readonly WriteLockingList<string> _blocking = [];
private readonly SemaphoreSlim _lock = new(1);
private readonly WriteLockingList<string> _muting = [];
public readonly List<IChannel> Channels = [];
public readonly EventService EventService = eventSvc;
public readonly WriteLockingList<Filter> Filters = [];
public readonly WriteLockingList<string> Following = [];
public readonly IServiceScope Scope = scopeFactory.CreateScope();
public readonly IServiceScopeFactory ScopeFactory = scopeFactory;
public readonly OauthToken Token = token;
public List<string> HiddenFromHome = [];
public readonly WriteLockingList<Filter> Filters = [];
public readonly WriteLockingList<string> Following = [];
private readonly WriteLockingList<string> _blocking = [];
private readonly WriteLockingList<string> _blockedBy = [];
private readonly WriteLockingList<string> _muting = [];
public void Dispose()
{
@ -362,9 +362,9 @@ public sealed class WebSocketConnection(
(note.Renote?.User != null &&
(IsFiltered(note.Renote.User) ||
IsFilteredMentions(note.Renote.Mentions))) ||
note.Renote?.Renote?.User != null &&
(note.Renote?.Renote?.User != null &&
(IsFiltered(note.Renote.Renote.User) ||
IsFilteredMentions(note.Renote.Renote.Mentions));
IsFilteredMentions(note.Renote.Renote.Mentions)));
public async Task CloseAsync(WebSocketCloseStatus status)
{

View file

@ -4,13 +4,13 @@ using System.Net.Mime;
using AsyncKeyedLock;
using Iceshrimp.Backend.Controllers.Attributes;
using Iceshrimp.Backend.Controllers.Renderers;
using Iceshrimp.Shared.Schemas;
using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Helpers;
using Iceshrimp.Backend.Core.Middleware;
using Iceshrimp.Backend.Core.Services;
using Iceshrimp.Shared.Schemas;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore;
@ -45,7 +45,7 @@ public class NoteController(
var note = await db.Notes.Where(p => p.Id == id)
.IncludeCommonProperties()
.EnsureVisibleFor(user)
.FilterHidden(user, db, filterOutgoingBlocks: false, filterMutes: false)
.FilterHidden(user, db, false, false)
.PrecomputeVisibilities(user)
.FirstOrDefaultAsync() ??
throw GracefulException.NotFound("Note not found");
@ -65,7 +65,7 @@ public class NoteController(
var note = await db.Notes.Where(p => p.Id == id)
.EnsureVisibleFor(user)
.FilterHidden(user, db, filterOutgoingBlocks: false, filterMutes: false)
.FilterHidden(user, db, false, false)
.FirstOrDefaultAsync() ??
throw GracefulException.NotFound("Note not found");
@ -94,7 +94,7 @@ public class NoteController(
var note = await db.Notes.Where(p => p.Id == id)
.EnsureVisibleFor(user)
.FilterHidden(user, db, filterOutgoingBlocks: false, filterMutes: false)
.FilterHidden(user, db, false, false)
.FirstOrDefaultAsync() ??
throw GracefulException.NotFound("Note not found");

View file

@ -2,10 +2,10 @@ using System.Net.Mime;
using Iceshrimp.Backend.Controllers.Attributes;
using Iceshrimp.Backend.Controllers.Renderers;
using Iceshrimp.Backend.Controllers.Schemas;
using Iceshrimp.Shared.Schemas;
using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Middleware;
using Iceshrimp.Shared.Schemas;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore;

View file

@ -1,10 +1,10 @@
using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Shared.Schemas;
using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Helpers;
using Iceshrimp.Backend.Core.Services;
using Iceshrimp.Shared.Schemas;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
@ -219,10 +219,10 @@ public class NoteRenderer(
public class NoteRendererDto
{
public List<NoteAttachment>? Attachments;
public List<NoteReactionSchema>? Reactions;
public List<UserResponse>? Users;
public List<EmojiResponse>? Emoji;
public List<Filter>? Filters;
public List<string>? LikedNotes;
public List<EmojiResponse>? Emoji;
public List<NoteReactionSchema>? Reactions;
public List<UserResponse>? Users;
}
}

View file

@ -1,6 +1,6 @@
using Iceshrimp.Shared.Schemas;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Shared.Schemas;
namespace Iceshrimp.Backend.Controllers.Renderers;

View file

@ -1,7 +1,7 @@
using Iceshrimp.Shared.Schemas;
using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Shared.Schemas;
using Microsoft.EntityFrameworkCore;
namespace Iceshrimp.Backend.Controllers.Renderers;
@ -91,14 +91,14 @@ public class UserProfileRenderer(DatabaseContext db)
public class RelationData
{
public required string UserId;
public required bool IsSelf;
public required bool IsFollowing;
public required bool IsBlocking;
public required bool IsFollowedBy;
public required bool IsFollowing;
public required bool IsMuting;
public required bool IsRequested;
public required bool IsRequestedBy;
public required bool IsBlocking;
public required bool IsMuting;
public required bool IsSelf;
public required string UserId;
public static implicit operator Relations(RelationData data)
{

View file

@ -1,8 +1,8 @@
using Iceshrimp.Shared.Schemas;
using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Shared.Schemas;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

View file

@ -2,12 +2,12 @@ using System.Net.Mime;
using Iceshrimp.Backend.Controllers.Attributes;
using Iceshrimp.Backend.Controllers.Renderers;
using Iceshrimp.Backend.Controllers.Schemas;
using Iceshrimp.Shared.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 Iceshrimp.Shared.Schemas;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore;

View file

@ -2,12 +2,12 @@ using System.Net.Mime;
using Iceshrimp.Backend.Controllers.Attributes;
using Iceshrimp.Backend.Controllers.Renderers;
using Iceshrimp.Backend.Controllers.Schemas;
using Iceshrimp.Shared.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 Iceshrimp.Shared.Schemas;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore;

View file

@ -17,10 +17,10 @@ public sealed class Config
public sealed class InstanceSection
{
public readonly string Version;
public readonly string Codename;
public readonly string? CommitHash;
public readonly string RawVersion;
public readonly string Version;
public InstanceSection()
{
@ -100,9 +100,9 @@ public sealed class Config
public sealed class StorageSection
{
public readonly TimeSpan? MediaRetentionTimeSpan;
public readonly int? MaxCacheSizeBytes;
public readonly int? MaxUploadSizeBytes;
public readonly TimeSpan? MediaRetentionTimeSpan;
public bool CleanAvatars = false;
public bool CleanBanners = false;

View file

@ -14,6 +14,13 @@ public static class Enums
ObjectStorage = 1
}
public enum ImageProcessor
{
ImageSharp,
LibVips,
None
}
public enum ItemVisibility
{
Hide = 0,
@ -21,13 +28,6 @@ public static class Enums
Public = 2
}
public enum Registrations
{
Closed = 0,
Invite = 1,
Open = 2
}
public enum PublicPreview
{
Lockdown = 0,
@ -36,10 +36,10 @@ public static class Enums
Public = 3
}
public enum ImageProcessor
public enum Registrations
{
ImageSharp,
LibVips,
None
Closed = 0,
Invite = 1,
Open = 2
}
}

View file

@ -7,6 +7,6 @@ public interface IEntity
public class EntityWrapper<T> : IEntity
{
public required string Id { get; init; }
public required T Entity { get; init; }
public required string Id { get; init; }
}

View file

@ -10,6 +10,13 @@ namespace Iceshrimp.Backend.Core.Database.Tables;
[Index("user_id")]
public class Filter
{
[PgName("filter_action_enum")]
public enum FilterAction
{
[PgName("warn")] Warn,
[PgName("hide")] Hide
}
[PgName("filter_context_enum")]
public enum FilterContext
{
@ -18,17 +25,11 @@ public class Filter
[PgName("threads")] Threads,
[PgName("notifications")] Notifications,
[PgName("accounts")] Accounts,
[PgName("public")] Public,
[PgName("public")] Public
}
[PgName("filter_action_enum")]
public enum FilterAction
{
[PgName("warn")] Warn,
[PgName("hide")] Hide,
}
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("id")]
public long Id { get; set; }

View file

@ -37,8 +37,7 @@ public class FollowRequest : IEntity
[StringLength(128)]
public string? RequestId { get; set; }
[Column("relationshipId")]
public Guid? RelationshipId { get; set; }
[Column("relationshipId")] public Guid? RelationshipId { get; set; }
/// <summary>
/// [Denormalized]

View file

@ -80,8 +80,7 @@ public class Following
[StringLength(512)]
public string? FolloweeSharedInbox { get; set; }
[Column("relationshipId")]
public Guid? RelationshipId { get; set; }
[Column("relationshipId")] public Guid? RelationshipId { get; set; }
[ForeignKey(nameof(FolloweeId))]
[InverseProperty(nameof(User.IncomingFollowRelationships))]

View file

@ -155,7 +155,8 @@ public class Note : IEntity
public string? ReplyUserId { get; set; }
/// <summary>
/// Mastodon requires a slightly differently computed replyUserId field. To save processing time, we do this ahead of time.
/// Mastodon requires a slightly differently computed replyUserId field. To save processing time, we do this ahead of
/// time.
/// </summary>
[Column("mastoReplyUserId")]
[StringLength(32)]

View file

@ -266,8 +266,7 @@ public class User : IEntity
[StringLength(128)]
public string? BannerBlurhash { get; set; }
[Column("splitDomainResolved")]
public bool SplitDomainResolved { get; set; }
[Column("splitDomainResolved")] public bool SplitDomainResolved { get; set; }
[InverseProperty(nameof(AbuseUserReport.Assignee))]
public virtual ICollection<AbuseUserReport> AbuseUserReportAssignees { get; set; } = new List<AbuseUserReport>();

View file

@ -1,7 +1,6 @@
using System.Buffers;
using System.Net;
using System.Text.Encodings.Web;
using System.Text.Json;
using Iceshrimp.Backend.Controllers.Attributes;
using Iceshrimp.Backend.Core.Middleware;
using Microsoft.AspNetCore.Mvc;
@ -9,6 +8,8 @@ using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using JsonSerializer = System.Text.Json.JsonSerializer;
namespace Iceshrimp.Backend.Core.Extensions;
@ -42,9 +43,9 @@ public static class MvcBuilderExtensions
public static IMvcBuilder ConfigureNewtonsoftJson(this IMvcBuilder builder)
{
Newtonsoft.Json.JsonConvert.DefaultSettings = () => new Newtonsoft.Json.JsonSerializerSettings
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc
DateTimeZoneHandling = DateTimeZoneHandling.Utc
};
return builder;

View file

@ -4,7 +4,6 @@ using System.Xml.Linq;
using Iceshrimp.Backend.Controllers.Federation;
using Iceshrimp.Backend.Controllers.Mastodon.Renderers;
using Iceshrimp.Backend.Controllers.Renderers;
using Iceshrimp.Shared.Schemas;
using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Federation.WebFinger;
@ -13,6 +12,7 @@ using Iceshrimp.Backend.Core.Middleware;
using Iceshrimp.Backend.Core.Services;
using Iceshrimp.Backend.Hubs.Authentication;
using Iceshrimp.Shared.Configuration;
using Iceshrimp.Shared.Schemas;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
@ -20,6 +20,7 @@ using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationM
using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.AspNetCore.DataProtection.Repositories;
using Microsoft.AspNetCore.Http.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging.Abstractions;
@ -120,7 +121,7 @@ public static class ServiceExtensions
.ConfigureWithValidation<Config.LocalStorageSection>(configuration, "Storage:Local")
.ConfigureWithValidation<Config.ObjectStorageSection>(configuration, "Storage:ObjectStorage");
services.Configure<Microsoft.AspNetCore.Http.Json.JsonOptions>(options =>
services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.PropertyNamingPolicy = JsonSerialization.Options.PropertyNamingPolicy;
foreach (var converter in JsonSerialization.Options.Converters)
@ -324,7 +325,8 @@ public static class HttpContextExtensions
/// <summary>
/// Async equivalent of EntityFrameworkCoreDataProtectionExtensions.PersistKeysToDbContext.
/// Required because Npgsql doesn't support the non-async APIs when using connection multiplexing, and the stock version EFCore API calls their blocking equivalents.
/// Required because Npgsql doesn't support the non-async APIs when using connection multiplexing, and the stock
/// version EFCore API calls their blocking equivalents.
/// </summary>
file static class DataProtectionExtensions
{

View file

@ -23,22 +23,6 @@ public static class SwaggerGenOptionsExtensions
options.DocInclusionPredicate(DocInclusionPredicate);
}
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Local",
Justification = "SwaggerGenOptions.SchemaFilter<T> instantiates this class at runtime")]
private class RequireNonNullablePropertiesSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema model, SchemaFilterContext context)
{
var additionalRequiredProps = model.Properties
.Where(x => !x.Value.Nullable && !model.Required.Contains(x.Key))
.Select(x => x.Key);
foreach (var propKey in additionalRequiredProps)
{
model.Required.Add(propKey);
}
}
}
private static bool DocInclusionPredicate(string docName, ApiDescription apiDesc)
{
if (!apiDesc.TryGetMethodInfo(out var methodInfo)) return false;
@ -61,6 +45,22 @@ public static class SwaggerGenOptionsExtensions
};
}
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Local",
Justification = "SwaggerGenOptions.SchemaFilter<T> instantiates this class at runtime")]
private class RequireNonNullablePropertiesSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema model, SchemaFilterContext context)
{
var additionalRequiredProps = model.Properties
.Where(x => !x.Value.Nullable && !model.Required.Contains(x.Key))
.Select(x => x.Key);
foreach (var propKey in additionalRequiredProps)
{
model.Required.Add(propKey);
}
}
}
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Local",
Justification = "SwaggerGenOptions.OperationFilter<T> instantiates this class at runtime")]
private class AuthorizeCheckOperationFilter : IOperationFilter

View file

@ -4,11 +4,11 @@ using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Migrations;
using Iceshrimp.Backend.Core.Middleware;
using Iceshrimp.Backend.Core.Services;
using Iceshrimp.WebPush;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.Extensions.Configuration.Ini;
using Microsoft.Extensions.Options;
using Iceshrimp.WebPush;
namespace Iceshrimp.Backend.Core.Extensions;

View file

@ -3,9 +3,9 @@ using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Helpers.LibMfm.Parsing;
using Iceshrimp.Backend.Core.Helpers.LibMfm.Serialization;
using static Iceshrimp.Parsing.MfmNodeTypes;
using Microsoft.Extensions.Options;
using Microsoft.FSharp.Collections;
using static Iceshrimp.Parsing.MfmNodeTypes;
namespace Iceshrimp.Backend.Core.Federation.ActivityPub;

View file

@ -18,14 +18,6 @@ public static class LdHelpers
private static readonly string Prefix = Path.Combine(AssemblyLocation, "contexts");
private static JToken GetPreloadedDocument(string filename) =>
JToken.Parse(File.ReadAllText(Path.Combine(Prefix, filename)));
private static RemoteDocument GetPreloadedContext(string filename) => new()
{
Document = GetPreloadedDocument(filename)
};
private static readonly Dictionary<string, RemoteDocument> PreloadedContexts = new()
{
{ "https://www.w3.org/ns/activitystreams", GetPreloadedContext("as.json") },
@ -66,6 +58,14 @@ public static class LdHelpers
private static IEnumerable<string> ASForceArray => ["tag", "attachment", "to", "cc", "bcc", "bto"];
private static JToken GetPreloadedDocument(string filename) =>
JToken.Parse(File.ReadAllText(Path.Combine(Prefix, filename)));
private static RemoteDocument GetPreloadedContext(string filename) => new()
{
Document = GetPreloadedDocument(filename)
};
private static RemoteDocument CustomLoader(Uri uri, JsonLdLoaderOptions jsonLdLoaderOptions)
{
var key = uri.AbsolutePath == "/schemas/litepub-0.1.jsonld" ? "litepub-0.1" : uri.ToString();

View file

@ -9,11 +9,10 @@ namespace Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
public class ASNote : ASObject
{
public ASNote(bool withType = true) => Type = withType ? Types.Note : null;
private string? _mkContent;
[JI] public bool VerifiedFetch = false;
private string? _mkContent;
public ASNote(bool withType = true) => Type = withType ? Types.Note : null;
[J("https://misskey-hub.net/ns#_misskey_content")]
[JC(typeof(VC))]

View file

@ -184,11 +184,11 @@ public static class HttpSignature
)
{
public readonly string Algo = algo;
public readonly IEnumerable<string> Headers = headers;
public readonly string KeyId = keyId;
public readonly byte[] Signature = signature;
public readonly string? Created = created;
public readonly string? Expires = expires;
public readonly IEnumerable<string> Headers = headers;
public readonly string KeyId = keyId;
public readonly string? Opaque = opaque;
public readonly byte[] Signature = signature;
}
}

View file

@ -4,12 +4,6 @@ namespace Iceshrimp.Backend.Core.Helpers;
public static class CryptographyHelpers
{
public static string GenerateRandomHexString(int length)
=> RandomNumberGenerator.GetHexString(length, true);
public static string GenerateRandomString(int length, Charset charset = Charset.AlphaNum)
=> RandomNumberGenerator.GetString(GetCharset(charset), length);
public enum Charset
{
AlphaNum,
@ -18,6 +12,17 @@ public static class CryptographyHelpers
CrockfordBase32Lower
}
private const string AlphaNumCharset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
private const string AlphaNumLowerCharset = "abcdefghijklmnopqrstuvwxyz0123456789";
private const string CrockfordBase32Charset = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
private const string CrockfordBase32LowerCharset = "0123456789abcdefghjkmnpqrstvwxyz";
public static string GenerateRandomHexString(int length)
=> RandomNumberGenerator.GetHexString(length, true);
public static string GenerateRandomString(int length, Charset charset = Charset.AlphaNum)
=> RandomNumberGenerator.GetString(GetCharset(charset), length);
private static string GetCharset(Charset charset) => charset switch
{
Charset.AlphaNum => AlphaNumCharset,
@ -26,9 +31,4 @@ public static class CryptographyHelpers
Charset.CrockfordBase32Lower => CrockfordBase32LowerCharset,
_ => throw new ArgumentOutOfRangeException(nameof(charset), charset, null)
};
private const string AlphaNumCharset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
private const string AlphaNumLowerCharset = "abcdefghijklmnopqrstuvwxyz0123456789";
private const string CrockfordBase32Charset = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
private const string CrockfordBase32LowerCharset = "0123456789abcdefghjkmnpqrstvwxyz";
}

View file

@ -2,8 +2,8 @@ namespace Iceshrimp.Backend.Core.Helpers;
public sealed class AsyncAutoResetEvent(bool signaled = false)
{
private readonly List<TaskCompletionSource<bool>> _taskCompletionSources = [];
private readonly List<TaskCompletionSource<bool>> _noResetTaskCompletionSources = [];
private readonly List<TaskCompletionSource<bool>> _taskCompletionSources = [];
public bool Signaled => signaled;
public Task<bool> WaitAsync(CancellationToken cancellationToken = default)

View file

@ -77,7 +77,7 @@ public static class FilterHelper
return keyword;
}
else if ((note.Text != null && note.Text.Contains(keyword, StringComparison.InvariantCultureIgnoreCase)) ||
note.Cw != null && note.Cw.Contains(keyword, StringComparison.InvariantCultureIgnoreCase))
(note.Cw != null && note.Cw.Contains(keyword, StringComparison.InvariantCultureIgnoreCase)))
{
return keyword;
}

View file

@ -8,9 +8,9 @@ using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Helpers.LibMfm.Parsing;
using static Iceshrimp.Parsing.MfmNodeTypes;
using Microsoft.Extensions.Options;
using Microsoft.FSharp.Collections;
using static Iceshrimp.Parsing.MfmNodeTypes;
using MfmHtmlParser = Iceshrimp.Backend.Core.Helpers.LibMfm.Parsing.HtmlParser;
using HtmlParser = AngleSharp.Html.Parser.HtmlParser;

View file

@ -6,13 +6,6 @@ namespace Iceshrimp.Backend.Core.Helpers;
public static class NoteThreadHelpers
{
public class TreeNode<T>(T self)
{
public readonly T Self = self;
public List<TreeNode<T>> Descendants = [];
public TreeNode<T>? Parent;
}
public static List<NoteResponse> OrderAncestors(this List<NoteResponse> notes)
{
var final = new List<NoteResponse>();
@ -139,4 +132,11 @@ public static class NoteThreadHelpers
return nodes;
}
public class TreeNode<T>(T self)
{
public readonly T Self = self;
public List<TreeNode<T>> Descendants = [];
public TreeNode<T>? Parent;
}
}

View file

@ -17,6 +17,23 @@ public class WriteLockingList<T>(IEnumerable<T>? sourceCollection = null) : ICol
lock (_list) _list.Add(item);
}
public void Clear()
{
lock (_list) _list.Clear();
}
public bool Contains(T item) => _list.Contains(item);
public void CopyTo(T[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex);
public bool Remove(T item)
{
lock (_list) return _list.Remove(item);
}
public int Count => _list.Count;
public bool IsReadOnly => ((ICollection<T>)_list).IsReadOnly;
public bool AddIfMissing(T item)
{
lock (_list)
@ -32,25 +49,8 @@ public class WriteLockingList<T>(IEnumerable<T>? sourceCollection = null) : ICol
lock (_list) _list.AddRange(item);
}
public void Clear()
{
lock (_list) _list.Clear();
}
public bool Contains(T item) => _list.Contains(item);
public void CopyTo(T[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex);
public bool Remove(T item)
{
lock (_list) return _list.Remove(item);
}
public int RemoveAll(Predicate<T> predicate)
{
lock (_list) return _list.RemoveAll(predicate);
}
public int Count => _list.Count;
public bool IsReadOnly => ((ICollection<T>)_list).IsReadOnly;
}

View file

@ -2,8 +2,8 @@ using System.Diagnostics.CodeAnalysis;
using System.Net;
using Iceshrimp.Backend.Controllers.Mastodon.Attributes;
using Iceshrimp.Backend.Controllers.Mastodon.Schemas;
using Iceshrimp.Shared.Schemas;
using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Shared.Schemas;
using Microsoft.Extensions.Options;
namespace Iceshrimp.Backend.Core.Middleware;

View file

@ -70,7 +70,7 @@ public class BackgroundTaskQueue(int parallelism)
await db.DriveFiles.AnyAsync(p => p.Id != file.Id &&
p.AccessKey == file.AccessKey &&
!p.IsLink,
cancellationToken: token);
token);
if (!deduplicated)
{
@ -128,7 +128,7 @@ public class BackgroundTaskQueue(int parallelism)
if (file.AccessKey == null) return;
var deduplicated =
await db.DriveFiles.AnyAsync(p => p.Id != file.Id && p.AccessKey == file.AccessKey && !p.IsLink,
cancellationToken: token);
token);
if (deduplicated)
return;
@ -267,7 +267,7 @@ public class BackgroundTaskQueue(int parallelism)
var dbInstance = await bgInstanceSvc.GetUpdatedInstanceMetadataAsync(user);
await bgDb.Instances.Where(p => p.Id == dbInstance.Id)
.ExecuteUpdateAsync(p => p.SetProperty(i => i.UsersCount, i => i.UsersCount - 1),
cancellationToken: token);
token);
});
logger.LogDebug("User {id} deleted successfully", jobData.UserId);

View file

@ -208,15 +208,15 @@ public class CustomHttpClient : HttpClient
private class RedirectHandler : DelegatingHandler
{
private int MaxAutomaticRedirections { get; set; }
private bool InitialAutoRedirect { get; set; }
public RedirectHandler(HttpMessageHandler innerHandler) : base(innerHandler)
{
var mostInnerHandler = innerHandler.GetMostInnerHandler();
SetupCustomAutoRedirect(mostInnerHandler);
}
private int MaxAutomaticRedirections { get; set; }
private bool InitialAutoRedirect { get; set; }
private void SetupCustomAutoRedirect(HttpMessageHandler? mostInnerHandler)
{
//Store the initial auto-redirect & max-auto-redirect values.

View file

@ -74,10 +74,10 @@ public class ImageProcessor
public class Result
{
public string? Blurhash;
public Func<Stream, Task>? RenderThumbnail;
public Func<Stream, Task>? RenderWebpublic;
public required DriveFile.FileProperties Properties;
public Func<Stream, Task>? RenderThumbnail;
public Func<Stream, Task>? RenderWebpublic;
}
public async Task<Result?> ProcessImage(Stream data, DriveFileCreationRequest request, bool genThumb, bool genWebp)

View file

@ -65,7 +65,7 @@ public class MetaService([FromKeyedServices("cache")] DatabaseContext db)
{
await db.MetaStore.Upsert(new MetaStoreEntry { Key = key, Value = value })
.On(p => p.Key)
.WhenMatched((_, orig) => new MetaStoreEntry { Value = orig.Value, })
.WhenMatched((_, orig) => new MetaStoreEntry { Value = orig.Value })
.RunAsync();
}
}

View file

@ -9,11 +9,11 @@ using Iceshrimp.Backend.Core.Helpers;
using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion;
using Iceshrimp.Backend.Core.Helpers.LibMfm.Parsing;
using Iceshrimp.Backend.Core.Helpers.LibMfm.Serialization;
using static Iceshrimp.Parsing.MfmNodeTypes;
using Iceshrimp.Backend.Core.Middleware;
using Iceshrimp.Backend.Core.Queues;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using static Iceshrimp.Parsing.MfmNodeTypes;
namespace Iceshrimp.Backend.Core.Services;
@ -49,8 +49,6 @@ public class NoteService(
)
{
private const int DefaultRecursionLimit = 100;
private readonly List<string> _resolverHistory = [];
private int _recursionLimit = DefaultRecursionLimit;
private static readonly AsyncKeyedLocker<string> KeyedLocker = new(o =>
{
@ -58,6 +56,9 @@ public class NoteService(
o.PoolInitialFill = 5;
});
private readonly List<string> _resolverHistory = [];
private int _recursionLimit = DefaultRecursionLimit;
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, Poll? poll = null,
@ -632,7 +633,8 @@ public class NoteService(
note.Visibility,
note.User.GetPublicUri(config.Value) +
"/followers"))
: ActivityPub.ActivityRenderer.RenderDelete(actor, new ASTombstone { Id = note.GetPublicUri(config.Value) });
: ActivityPub.ActivityRenderer.RenderDelete(actor,
new ASTombstone { Id = note.GetPublicUri(config.Value) });
if (note.Visibility == Note.NoteVisibility.Specified)
await deliverSvc.DeliverToAsync(activity, note.User, recipients.ToArray());

View file

@ -14,14 +14,14 @@ public class ObjectStorageService(IOptions<Config.StorageSection> config, HttpCl
{
private readonly string? _accessUrl = config.Value.ObjectStorage?.AccessUrl;
private readonly S3Bucket? _bucket = GetBucketSafely(config);
private readonly string? _prefix = config.Value.ObjectStorage?.Prefix?.Trim('/');
private readonly IReadOnlyDictionary<string, string>? _acl = config.Value.ObjectStorage?.SetAcl != null
? new Dictionary<string, string> { { "x-amz-acl", config.Value.ObjectStorage.SetAcl } }.AsReadOnly()
: null;
private readonly S3Bucket? _bucket = GetBucketSafely(config);
private readonly string? _prefix = config.Value.ObjectStorage?.Prefix?.Trim('/');
private static S3Bucket? GetBucketSafely(IOptions<Config.StorageSection> config)
{
if (config.Value.Provider != Enums.FileStorage.Local) return GetBucket(config);

View file

@ -8,9 +8,9 @@ using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Middleware;
using Iceshrimp.WebPush;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Iceshrimp.WebPush;
using PushSubscription = Iceshrimp.Backend.Core.Database.Tables.PushSubscription;
using WebPushSubscription = Iceshrimp.WebPush.PushSubscription;

View file

@ -22,13 +22,13 @@ public class QueueService(
) : BackgroundService
{
private readonly List<IPostgresJobQueue> _queues = [];
public IEnumerable<string> QueueNames => _queues.Select(p => p.Name);
public readonly BackgroundTaskQueue BackgroundTaskQueue = new(queueConcurrency.Value.BackgroundTask);
public readonly DeliverQueue DeliverQueue = new(queueConcurrency.Value.Deliver);
public readonly InboxQueue InboxQueue = new(queueConcurrency.Value.Inbox);
public readonly DeliverQueue DeliverQueue = new(queueConcurrency.Value.Deliver);
public readonly PreDeliverQueue PreDeliverQueue = new(queueConcurrency.Value.PreDeliver);
public readonly BackgroundTaskQueue BackgroundTaskQueue = new(queueConcurrency.Value.BackgroundTask);
public IEnumerable<string> QueueNames => _queues.Select(p => p.Name);
private static async Task<NpgsqlConnection> GetNpgsqlConnection(IServiceScope scope)
{
@ -263,13 +263,13 @@ public interface IPostgresJobQueue
{
public string Name { get; }
public TimeSpan Timeout { get; }
public Task ExecuteAsync(IServiceScopeFactory scopeFactory, CancellationToken token, CancellationToken queueToken);
public Task RecoverOrPrepareForExitAsync();
public void RaiseJobQueuedEvent();
public void RaiseJobDelayedEvent();
public TimeSpan Timeout { get; }
}
public class PostgresJobQueue<T>(
@ -279,14 +279,13 @@ public class PostgresJobQueue<T>(
TimeSpan timeout
) : IPostgresJobQueue where T : class
{
public string Name => name;
public TimeSpan Timeout => timeout;
private readonly AsyncAutoResetEvent _delayedChannel = new();
private readonly AsyncAutoResetEvent _queuedChannel = new();
private IServiceScopeFactory _scopeFactory = null!;
private ILogger<QueueService> _logger = null!;
private IServiceScopeFactory _scopeFactory = null!;
private string? _workerId;
public string Name => name;
public TimeSpan Timeout => timeout;
public void RaiseJobQueuedEvent() => QueuedChannelEvent?.Invoke(null, EventArgs.Empty);
public void RaiseJobDelayedEvent() => DelayedChannelEvent?.Invoke(null, EventArgs.Empty);

View file

@ -12,20 +12,17 @@ namespace Iceshrimp.Backend.Core.Services;
public sealed class StreamingService
{
private readonly ConcurrentDictionary<string, StreamingConnectionAggregate> _connections = [];
private readonly IHubContext<StreamingHub, IStreamingHubClient> _hub;
private readonly EventService _eventSvc;
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger<StreamingService> _logger;
private static readonly AsyncKeyedLocker<string> Locker = new(o =>
{
o.PoolSize = 100;
o.PoolInitialFill = 5;
});
public event EventHandler<(Note note, Func<Task<NoteResponse>> rendered)>? NotePublished;
public event EventHandler<(Note note, Func<Task<NoteResponse>> rendered)>? NoteUpdated;
private readonly ConcurrentDictionary<string, StreamingConnectionAggregate> _connections = [];
private readonly EventService _eventSvc;
private readonly IHubContext<StreamingHub, IStreamingHubClient> _hub;
private readonly ILogger<StreamingService> _logger;
private readonly IServiceScopeFactory _scopeFactory;
public StreamingService(
IHubContext<StreamingHub, IStreamingHubClient> hub,
@ -43,6 +40,9 @@ public sealed class StreamingService
eventSvc.NoteUpdated += OnNoteUpdated;
}
public event EventHandler<(Note note, Func<Task<NoteResponse>> rendered)>? NotePublished;
public event EventHandler<(Note note, Func<Task<NoteResponse>> rendered)>? NoteUpdated;
public void Connect(string userId, User user, string connectionId)
{
_connections

View file

@ -5,8 +5,8 @@ using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion;
using Iceshrimp.Backend.Core.Helpers.LibMfm.Parsing;
using static Iceshrimp.Parsing.MfmNodeTypes;
using Microsoft.Extensions.Options;
using static Iceshrimp.Parsing.MfmNodeTypes;
namespace Iceshrimp.Backend.Core.Services;

View file

@ -166,7 +166,7 @@ public class UserService(
//TODO: FollowersCount
//TODO: FollowingCount
Emojis = emoji.Select(p => p.Id).ToList(),
Tags = tags,
Tags = tags
};
var profile = new UserProfile

View file

@ -1,5 +1,6 @@
using Iceshrimp.Shared.HubSchemas;
using Microsoft.AspNetCore.SignalR;
namespace Iceshrimp.Backend.Hubs;
public class ExampleHub : Hub<IExampleHubClient>, IExampleHubServer

View file

@ -18,114 +18,49 @@ namespace Iceshrimp.Backend.Hubs.Helpers;
[MustDisposeResource]
public sealed class StreamingConnectionAggregate : IDisposable
{
private readonly User _user;
private readonly string _userId;
private readonly WriteLockingList<string> _blockedBy = [];
private readonly WriteLockingList<string> _blocking = [];
private readonly WriteLockingList<string> _connectionIds = [];
private readonly IHubContext<StreamingHub, IStreamingHubClient> _hub;
private readonly EventService _eventService;
private readonly WriteLockingList<string> _following = [];
private readonly IHubContext<StreamingHub, IStreamingHubClient> _hub;
private readonly ILogger _logger;
private readonly WriteLockingList<string> _muting = [];
private readonly IServiceScope _scope;
private readonly IServiceScopeFactory _scopeFactory;
private readonly StreamingService _streamingService;
private readonly ILogger _logger;
private readonly WriteLockingList<string> _following = [];
private readonly WriteLockingList<string> _muting = [];
private readonly WriteLockingList<string> _blocking = [];
private readonly WriteLockingList<string> _blockedBy = [];
private List<string> _hiddenFromHome = [];
private readonly ConcurrentDictionary<string, WriteLockingList<StreamingTimeline>> _subscriptions = [];
private readonly User _user;
private readonly string _userId;
private List<string> _hiddenFromHome = [];
public bool HasSubscribers => _connectionIds.Count != 0;
#region Destruction
public void Dispose()
{
DisconnectAll();
_streamingService.NotePublished -= OnNotePublished;
_streamingService.NoteUpdated -= OnNoteUpdated;
_eventService.Notification -= OnNotification;
_eventService.UserBlocked -= OnUserBlock;
_eventService.UserUnblocked -= OnUserUnblock;
_eventService.UserMuted -= OnUserMute;
_eventService.UserUnmuted -= OnUserUnmute;
_eventService.UserFollowed -= OnUserFollow;
_eventService.UserUnfollowed -= OnUserUnfollow;
_scope.Dispose();
}
#endregion
private AsyncServiceScope GetTempScope() => _scopeFactory.CreateAsyncScope();
#region Initialization
public StreamingConnectionAggregate(
string userId,
User user,
IHubContext<StreamingHub, IStreamingHubClient> hub,
EventService eventSvc,
IServiceScopeFactory scopeFactory, StreamingService streamingService
)
{
if (userId != user.Id)
throw new Exception("userId doesn't match user.Id");
_userId = userId;
_user = user;
_hub = hub;
_eventService = eventSvc;
_scope = scopeFactory.CreateScope();
_scopeFactory = scopeFactory;
_streamingService = streamingService;
_logger = _scope.ServiceProvider.GetRequiredService<ILogger<StreamingConnectionAggregate>>();
_ = InitializeAsync();
}
private async Task InitializeAsync()
{
_eventService.UserBlocked += OnUserBlock;
_eventService.UserUnblocked += OnUserUnblock;
_eventService.UserMuted += OnUserMute;
_eventService.UserUnmuted += OnUserUnmute;
_eventService.UserFollowed += OnUserFollow;
_eventService.UserUnfollowed += OnUserUnfollow;
await InitializeRelationships();
_eventService.Notification += OnNotification;
_streamingService.NotePublished += OnNotePublished;
_streamingService.NoteUpdated += OnNoteUpdated;
}
private async Task InitializeRelationships()
{
await using var scope = GetTempScope();
await using var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
_following.AddRange(await db.Followings.Where(p => p.Follower == _user)
.Select(p => p.FolloweeId)
.ToListAsync());
_blocking.AddRange(await db.Blockings.Where(p => p.Blocker == _user)
.Select(p => p.BlockeeId)
.ToListAsync());
_blockedBy.AddRange(await db.Blockings.Where(p => p.Blockee == _user)
.Select(p => p.BlockerId)
.ToListAsync());
_muting.AddRange(await db.Mutings.Where(p => p.Muter == _user)
.Select(p => p.MuteeId)
.ToListAsync());
_hiddenFromHome = await db.UserListMembers
.Where(p => p.UserList.User == _user && p.UserList.HideFromHomeTl)
.Select(p => p.UserId)
.Distinct()
.ToListAsync();
}
#endregion
#region Channel subscription handlers
public void Subscribe(string connectionId, StreamingTimeline timeline)
{
if (!_connectionIds.Contains(connectionId)) return;
_subscriptions.GetOrAdd(connectionId, []).Add(timeline);
}
public void Unsubscribe(string connectionId, StreamingTimeline timeline)
{
if (!_connectionIds.Contains(connectionId)) return;
_subscriptions.TryGetValue(connectionId, out var collection);
collection?.Remove(timeline);
}
#endregion
private async void OnNotification(object? _, Notification notification)
{
try
@ -228,13 +163,6 @@ public sealed class StreamingConnectionAggregate : IDisposable
return wrapped;
}
private class NoteWithVisibilities(Note note)
{
public readonly Note Note = note;
public Note? Reply = note.Reply;
public Note? Renote = note.Renote;
}
private (List<string> connectionIds, List<StreamingTimeline> timelines) FindRecipients(Note note)
{
List<StreamingTimeline> timelines = [];
@ -260,6 +188,97 @@ public sealed class StreamingConnectionAggregate : IDisposable
return (connectionIds, timelines);
}
private class NoteWithVisibilities(Note note)
{
public readonly Note Note = note;
public Note? Renote = note.Renote;
public Note? Reply = note.Reply;
}
#region Initialization
public StreamingConnectionAggregate(
string userId,
User user,
IHubContext<StreamingHub, IStreamingHubClient> hub,
EventService eventSvc,
IServiceScopeFactory scopeFactory, StreamingService streamingService
)
{
if (userId != user.Id)
throw new Exception("userId doesn't match user.Id");
_userId = userId;
_user = user;
_hub = hub;
_eventService = eventSvc;
_scope = scopeFactory.CreateScope();
_scopeFactory = scopeFactory;
_streamingService = streamingService;
_logger = _scope.ServiceProvider.GetRequiredService<ILogger<StreamingConnectionAggregate>>();
_ = InitializeAsync();
}
private async Task InitializeAsync()
{
_eventService.UserBlocked += OnUserBlock;
_eventService.UserUnblocked += OnUserUnblock;
_eventService.UserMuted += OnUserMute;
_eventService.UserUnmuted += OnUserUnmute;
_eventService.UserFollowed += OnUserFollow;
_eventService.UserUnfollowed += OnUserUnfollow;
await InitializeRelationships();
_eventService.Notification += OnNotification;
_streamingService.NotePublished += OnNotePublished;
_streamingService.NoteUpdated += OnNoteUpdated;
}
private async Task InitializeRelationships()
{
await using var scope = GetTempScope();
await using var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
_following.AddRange(await db.Followings.Where(p => p.Follower == _user)
.Select(p => p.FolloweeId)
.ToListAsync());
_blocking.AddRange(await db.Blockings.Where(p => p.Blocker == _user)
.Select(p => p.BlockeeId)
.ToListAsync());
_blockedBy.AddRange(await db.Blockings.Where(p => p.Blockee == _user)
.Select(p => p.BlockerId)
.ToListAsync());
_muting.AddRange(await db.Mutings.Where(p => p.Muter == _user)
.Select(p => p.MuteeId)
.ToListAsync());
_hiddenFromHome = await db.UserListMembers
.Where(p => p.UserList.User == _user && p.UserList.HideFromHomeTl)
.Select(p => p.UserId)
.Distinct()
.ToListAsync();
}
#endregion
#region Channel subscription handlers
public void Subscribe(string connectionId, StreamingTimeline timeline)
{
if (!_connectionIds.Contains(connectionId)) return;
_subscriptions.GetOrAdd(connectionId, []).Add(timeline);
}
public void Unsubscribe(string connectionId, StreamingTimeline timeline)
{
if (!_connectionIds.Contains(connectionId)) return;
_subscriptions.TryGetValue(connectionId, out var collection);
collection?.Remove(timeline);
}
#endregion
#region Relationship change event handlers
private void OnUserBlock(object? _, UserInteraction interaction)
@ -373,23 +392,4 @@ public sealed class StreamingConnectionAggregate : IDisposable
}
#endregion
#region Destruction
public void Dispose()
{
DisconnectAll();
_streamingService.NotePublished -= OnNotePublished;
_streamingService.NoteUpdated -= OnNoteUpdated;
_eventService.Notification -= OnNotification;
_eventService.UserBlocked -= OnUserBlock;
_eventService.UserUnblocked -= OnUserUnblock;
_eventService.UserMuted -= OnUserMute;
_eventService.UserUnmuted -= OnUserUnmute;
_eventService.UserFollowed -= OnUserFollow;
_eventService.UserUnfollowed -= OnUserUnfollow;
_scope.Dispose();
}
#endregion
}

View file

@ -1,5 +1,5 @@
@page "/"
@model Iceshrimp.Backend.Pages.IndexModel
@model IndexModel
@{
ViewData["title"] = $"Home - {Model.InstanceName}";

View file

@ -7,9 +7,9 @@ namespace Iceshrimp.Backend.Pages;
public class IndexModel(MetaService meta) : PageModel
{
public string InstanceName = null!;
public string InstanceDescription = null!;
public string? ContactEmail;
public string InstanceDescription = null!;
public string InstanceName = null!;
public async Task<IActionResult> OnGet()
{

View file

@ -1,7 +1,7 @@
@page "/notes/{id}"
@using Iceshrimp.Backend.Core.Database.Tables
@using Iceshrimp.Backend.Core.Extensions
@model Iceshrimp.Backend.Pages.NoteModel
@model NoteModel
@if (Model.Note == null)
{

View file

@ -19,13 +19,13 @@ public class NoteModel(
MfmConverter mfmConverter
) : PageModel
{
public Dictionary<string, List<DriveFile>> MediaAttachments = new();
public Note? Note;
public string? QuoteUrl;
public Dictionary<string, string> TextContent = new();
public Dictionary<string, List<DriveFile>> MediaAttachments = new();
public bool ShowMedia = security.Value.PublicPreview > Enums.PublicPreview.RestrictedNoMedia;
public bool ShowRemoteReplies = security.Value.PublicPreview > Enums.PublicPreview.Restricted;
public Dictionary<string, string> TextContent = new();
[SuppressMessage("ReSharper", "EntityFramework.NPlusOne.IncompleteDataQuery",
Justification = "IncludeCommonProperties")]

View file

@ -24,6 +24,7 @@
width: 3em;
max-height: 3em;
}
.title {
grid-column: 2/-1;
grid-row: 1;
@ -31,6 +32,7 @@
color: var(--text-bright);
font-weight: 600;
}
.acct {
grid-column: 2/-1;
grid-row: 2;

View file

@ -1,8 +1,8 @@
@page "/queue/{queue?}/{pagination:int?}/{status?}"
@inject QueueService QueueSvc
@using Iceshrimp.Backend.Core.Database.Tables
@using Iceshrimp.Backend.Core.Extensions
@using Iceshrimp.Backend.Core.Services
@inject QueueService QueueSvc
@model QueueModel
@{

View file

@ -10,15 +10,15 @@ namespace Iceshrimp.Backend.Pages;
public class QueueModel(DatabaseContext db, QueueService queueSvc) : PageModel
{
public List<Job> Jobs = [];
public string? Queue;
public int? DelayedCount;
public Job.JobStatus? Filter;
public int? TotalCount;
public List<Job> Jobs = [];
public int? NextPage;
public int? PrevPage;
public string? Queue;
public int? QueuedCount;
public int? RunningCount;
public int? DelayedCount;
public int? PrevPage;
public int? NextPage;
public int? TotalCount;
public async Task<IActionResult> OnGet(
[FromRoute] string? queue, [FromRoute(Name = "pagination")] int? page, [FromRoute] string? status

View file

@ -42,7 +42,9 @@
{
<tr>
<td>Actions</td>
<td><a class="fake-link" onclick="retry('@Model.Job.Id.ToStringLower()')">Retry</a></td>
<td>
<a class="fake-link" onclick="retry('@Model.Job.Id.ToStringLower()')">Retry</a>
</td>
</tr>
}
<tr>

View file

@ -9,8 +9,17 @@ namespace Iceshrimp.Backend.Pages;
public class QueueJobModel(DatabaseContext db) : PageModel
{
private static Dictionary<string, string> _lookup = new()
{
{ "inbox", "body" },
{ "deliver", "payload" },
{ "pre-deliver", "serializedActivity" }
};
public Job Job = null!;
public Dictionary<string, string> Lookup => _lookup;
public async Task<IActionResult> OnGet([FromRoute] Guid id)
{
if (!Request.Cookies.TryGetValue("admin_session", out var cookie))
@ -22,13 +31,4 @@ public class QueueJobModel(DatabaseContext db) : PageModel
throw GracefulException.NotFound($"Job {id} not found");
return Page();
}
private static Dictionary<string, string> _lookup = new()
{
{ "inbox", "body" },
{ "deliver", "payload" },
{ "pre-deliver", "serializedActivity" }
};
public Dictionary<string, string> Lookup => _lookup;
}