[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 //FIXME Implement members
Users = new NodeInfoResponse.NodeInfoUsers Users = new NodeInfoResponse.NodeInfoUsers
{ {
Total = totalUsers, ActiveMonth = activeMonth, ActiveHalfYear = activeHalfYear Total = totalUsers,
ActiveMonth = activeMonth,
ActiveHalfYear = activeHalfYear
}, },
LocalComments = 0, LocalComments = 0,
LocalPosts = localPosts 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)] [XmlRoot("XRD", Namespace = "http://docs.oasis-open.org/ns/xri/xrd-1.0", IsNullable = false)]
public class HostMetaXmlResponse() public class HostMetaXmlResponse()
{ {
[XmlElement("Link")] public required HostMetaXmlResponseLink Link;
[SetsRequiredMembers] [SetsRequiredMembers]
public HostMetaXmlResponse(string webDomain) : this() => Link = new HostMetaXmlResponseLink(webDomain); public HostMetaXmlResponse(string webDomain) : this() => Link = new HostMetaXmlResponseLink(webDomain);
[XmlElement("Link")] public required HostMetaXmlResponseLink Link;
} }
public class HostMetaXmlResponseLink() public class HostMetaXmlResponseLink()
{ {
[XmlAttribute("rel")] public string Rel = "lrdd";
[XmlAttribute("template")] public required string Template;
[XmlAttribute("type")] public string Type = "application/xrd+xml";
[SetsRequiredMembers] [SetsRequiredMembers]
public HostMetaXmlResponseLink(string webDomain) : this() => public HostMetaXmlResponseLink(string webDomain) : this() =>
Template = $"https://{webDomain}/.well-known/webfinger?resource={{uri}}"; 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 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 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) if (request.Fields?.Where(p => p is { Name: not null, Value: not null }).ToList() is { Count: > 0 } fields)
{ {
user.UserProfile.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(); .ToArray();
} }
@ -87,7 +92,9 @@ public class AccountController(
{ {
var rq = new DriveFileCreationRequest 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); var avatar = await driveSvc.StoreFile(request.Avatar.OpenReadStream(), user, rq);
user.Avatar = avatar; user.Avatar = avatar;
@ -99,7 +106,9 @@ public class AccountController(
{ {
var rq = new DriveFileCreationRequest 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); var banner = await driveSvc.StoreFile(request.Banner.OpenReadStream(), user, rq);
user.Banner = banner; user.Banner = banner;
@ -282,11 +291,11 @@ public class AccountController(
throw GracefulException.BadRequest("You cannot block yourself"); throw GracefulException.BadRequest("You cannot block yourself");
var blockee = await db.Users var blockee = await db.Users
.Where(p => p.Id == id) .Where(p => p.Id == id)
.IncludeCommonProperties() .IncludeCommonProperties()
.PrecomputeRelationshipData(user) .PrecomputeRelationshipData(user)
.FirstOrDefaultAsync() ?? .FirstOrDefaultAsync() ??
throw GracefulException.RecordNotFound(); throw GracefulException.RecordNotFound();
await userSvc.BlockUserAsync(user, blockee); await userSvc.BlockUserAsync(user, blockee);
@ -305,11 +314,11 @@ public class AccountController(
throw GracefulException.BadRequest("You cannot unblock yourself"); throw GracefulException.BadRequest("You cannot unblock yourself");
var blockee = await db.Users var blockee = await db.Users
.Where(p => p.Id == id) .Where(p => p.Id == id)
.IncludeCommonProperties() .IncludeCommonProperties()
.PrecomputeRelationshipData(user) .PrecomputeRelationshipData(user)
.FirstOrDefaultAsync() ?? .FirstOrDefaultAsync() ??
throw GracefulException.RecordNotFound(); throw GracefulException.RecordNotFound();
await userSvc.UnblockUserAsync(user, blockee); await userSvc.UnblockUserAsync(user, blockee);

View file

@ -145,9 +145,9 @@ public class ConversationsController(
private class Conversation private class Conversation
{ {
public required string Id { get; init; }
public required Note LastNote; public required Note LastNote;
public required List<User> Users;
public required bool Unread; 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 var res = await db.UserLists
.Where(p => p.User == user) .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(); .ToListAsync();
return Ok(res); return Ok(res);
@ -50,7 +55,12 @@ public class ListController(DatabaseContext db, UserRenderer userRenderer) : Con
var res = await db.UserLists var res = await db.UserLists
.Where(p => p.User == user && p.Id == id) .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() ?? .FirstOrDefaultAsync() ??
throw GracefulException.RecordNotFound(); throw GracefulException.RecordNotFound();
@ -79,7 +89,12 @@ public class ListController(DatabaseContext db, UserRenderer userRenderer) : Con
await db.AddAsync(list); await db.AddAsync(list);
await db.SaveChangesAsync(); 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); return Ok(res);
} }
@ -105,7 +120,12 @@ public class ListController(DatabaseContext db, UserRenderer userRenderer) : Con
db.Update(list); db.Update(list);
await db.SaveChangesAsync(); 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); return Ok(res);
} }

View file

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

View file

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

View file

@ -4,6 +4,10 @@ namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
public class AnnouncementEntity 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("id")] public required string Id { get; set; }
[J("content")] public required string Content { get; set; } [J("content")] public required string Content { get; set; }
[J("published_at")] public required string PublishedAt { 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("mentions")] public required List<MentionEntity> Mentions { get; set; }
[J("emojis")] public required List<EmojiEntity> Emoji { 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("published")] public bool Published => true;
[J("all_day")] public bool AllDay => false; [J("all_day")] public bool AllDay => false;
} }

View file

@ -5,8 +5,8 @@ namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
public class ConversationEntity : IEntity public class ConversationEntity : IEntity
{ {
[J("id")] public required string Id { get; set; }
[J("unread")] public required bool Unread { get; set; } [J("unread")] public required bool Unread { get; set; }
[J("accounts")] public required List<AccountEntity> Accounts { get; set; } [J("accounts")] public required List<AccountEntity> Accounts { get; set; }
[J("last_status")] public required StatusEntity LastStatus { 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 public class PollEntity : IEntity
{ {
[J("id")] public required string Id { get; set; }
[J("expires_at")] public required string? ExpiresAt { get; set; } [J("expires_at")] public required string? ExpiresAt { get; set; }
[J("expired")] public required bool Expired { get; set; } [J("expired")] public required bool Expired { get; set; }
[J("multiple")] public required bool Multiple { 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("options")] public required List<PollOptionEntity> Options { get; set; }
[J("emojis")] public List<EmojiEntity> Emoji => []; //TODO [J("emojis")] public List<EmojiEntity> Emoji => []; //TODO
[J("id")] public required string Id { get; set; }
} }
public class PollOptionEntity public class PollOptionEntity

View file

@ -9,7 +9,6 @@ namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
public class StatusEntity : IEntity public class StatusEntity : IEntity
{ {
[J("id")] public required string Id { get; set; }
[J("content")] public required string? Content { get; set; } [J("content")] public required string? Content { get; set; }
[J("uri")] public required string Uri { get; set; } [J("uri")] public required string Uri { get; set; }
[J("url")] public required string Url { get; set; } [J("url")] public required string Url { get; set; }
@ -52,7 +51,8 @@ public class StatusEntity : IEntity
[J("card")] public object? Card => null; //FIXME [J("card")] public object? Card => null; //FIXME
[J("application")] public object? Application => null; //FIXME [J("application")] public object? Application => null; //FIXME
[J("language")] public string? Language => null; //FIXME [J("language")] public string? Language => null; //FIXME
[J("id")] public required string Id { get; set; }
public static string EncodeVisibility(Note.NoteVisibility visibility) public static string EncodeVisibility(Note.NoteVisibility visibility)
{ {

View file

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

View file

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

View file

@ -12,13 +12,13 @@ public class PublicChannel(
bool onlyMedia bool onlyMedia
) : IChannel ) : IChannel
{ {
public readonly ILogger<PublicChannel> Logger =
connection.ScopeFactory.CreateScope().ServiceProvider.GetRequiredService<ILogger<PublicChannel>>();
public string Name => name; public string Name => name;
public List<string> Scopes => ["read:statuses"]; public List<string> Scopes => ["read:statuses"];
public bool IsSubscribed { get; private set; } public bool IsSubscribed { get; private set; }
public readonly ILogger<PublicChannel> Logger =
connection.ScopeFactory.CreateScope().ServiceProvider.GetRequiredService<ILogger<PublicChannel>>();
public Task Subscribe(StreamingRequestMessage _) public Task Subscribe(StreamingRequestMessage _)
{ {
if (IsSubscribed) return Task.CompletedTask; if (IsSubscribed) return Task.CompletedTask;
@ -65,7 +65,9 @@ public class PublicChannel(
var rendered = await renderer.RenderAsync(note, connection.Token.User); var rendered = await renderer.RenderAsync(note, connection.Token.User);
var message = new StreamingUpdateMessage 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)); await connection.SendMessageAsync(JsonSerializer.Serialize(message));
} }
@ -85,7 +87,9 @@ public class PublicChannel(
var rendered = await renderer.RenderAsync(note, connection.Token.User); var rendered = await renderer.RenderAsync(note, connection.Token.User);
var message = new StreamingUpdateMessage 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)); await connection.SendMessageAsync(JsonSerializer.Serialize(message));
} }
@ -100,7 +104,12 @@ public class PublicChannel(
try try
{ {
if (!IsApplicable(note)) return; 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)); await connection.SendMessageAsync(JsonSerializer.Serialize(message));
} }
catch (Exception e) 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 class UserChannel(WebSocketConnection connection, bool notificationsOnly) : IChannel
{ {
public readonly ILogger<UserChannel> Logger =
connection.ScopeFactory.CreateScope().ServiceProvider.GetRequiredService<ILogger<UserChannel>>();
private List<string> _followedUsers = []; private List<string> _followedUsers = [];
public string Name => notificationsOnly ? "user:notification" : "user"; public string Name => notificationsOnly ? "user:notification" : "user";
public List<string> Scopes => ["read:statuses", "read:notifications"]; public List<string> Scopes => ["read:statuses", "read:notifications"];
public bool IsSubscribed { get; private set; } public bool IsSubscribed { get; private set; }
public readonly ILogger<UserChannel> Logger =
connection.ScopeFactory.CreateScope().ServiceProvider.GetRequiredService<ILogger<UserChannel>>();
public async Task Subscribe(StreamingRequestMessage _) public async Task Subscribe(StreamingRequestMessage _)
{ {
if (IsSubscribed) return; if (IsSubscribed) return;
@ -74,7 +74,9 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly)
var rendered = await renderer.RenderAsync(note, connection.Token.User); var rendered = await renderer.RenderAsync(note, connection.Token.User);
var message = new StreamingUpdateMessage 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)); 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 rendered = await renderer.RenderAsync(note, connection.Token.User);
var message = new StreamingUpdateMessage 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)); await connection.SendMessageAsync(JsonSerializer.Serialize(message));
} }
@ -109,7 +113,12 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly)
try try
{ {
if (!IsApplicable(note)) return; 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)); await connection.SendMessageAsync(JsonSerializer.Serialize(message));
} }
catch (Exception e) catch (Exception e)
@ -139,7 +148,9 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly)
var message = new StreamingUpdateMessage 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)); 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.Renderers;
using Iceshrimp.Backend.Controllers.Schemas; using Iceshrimp.Backend.Controllers.Schemas;
using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions; using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.Backend.Core.Middleware;
using Iceshrimp.Backend.Core.Services;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting; using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -14,7 +12,8 @@ using Microsoft.EntityFrameworkCore;
namespace Iceshrimp.Backend.Controllers; namespace Iceshrimp.Backend.Controllers;
[ApiController] [ApiController]
[Authenticate, Authorize] [Authenticate]
[Authorize]
[EnableRateLimiting("sliding")] [EnableRateLimiting("sliding")]
[Route("/api/iceshrimp/v1/notification")] [Route("/api/iceshrimp/v1/notification")]
[Produces(MediaTypeNames.Application.Json)] [Produces(MediaTypeNames.Application.Json)]

View file

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

View file

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

View file

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

View file

@ -1,5 +1,4 @@
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
using JI = System.Text.Json.Serialization.JsonIgnoreAttribute;
namespace Iceshrimp.Backend.Controllers.Schemas; 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<Webhook> Webhooks { get; init; } = null!;
public virtual DbSet<AllowedInstance> AllowedInstances { get; init; } = null!; public virtual DbSet<AllowedInstance> AllowedInstances { get; init; } = null!;
public virtual DbSet<BlockedInstance> BlockedInstances { 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<MetaStoreEntry> MetaStore { get; init; } = null!;
public virtual DbSet<DataProtectionKey> DataProtectionKeys { get; init; } = null!;
public static NpgsqlDataSource GetDataSource(Config.DatabaseSection? config) public static NpgsqlDataSource GetDataSource(Config.DatabaseSection? config)
{ {

View file

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

View file

@ -8,10 +8,10 @@ namespace Iceshrimp.Backend.Core.Database.Tables;
[Index("Name", IsUnique = true)] [Index("Name", IsUnique = true)]
public class Hashtag : IEntity public class Hashtag : IEntity
{ {
[Column("name")] [StringLength(128)] public string Name { get; set; } = null!;
[Key] [Key]
[Column("id")] [Column("id")]
[StringLength(32)] [StringLength(32)]
public string Id { get; set; } = null!; 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")] [PrimaryKey("UserId", "Type")]
public class Marker public class Marker
{ {
[Column("userId")] [PgName("marker_type_enum")]
[StringLength(32)] public enum MarkerType
public string UserId { get; set; } = null!; {
[PgName("home")] Home,
[PgName("notifications")] Notifications
}
[Column("type")] [Column("userId")] [StringLength(32)] public string UserId { get; set; } = null!;
[StringLength(32)]
public MarkerType Type { get; set; } [Column("type")] [StringLength(32)] public MarkerType Type { get; set; }
[Column("position")] [Column("position")]
[StringLength(32)] [StringLength(32)]
@ -30,11 +33,4 @@ public class Marker
[ForeignKey("UserId")] [ForeignKey("UserId")]
[InverseProperty(nameof(Tables.User.Markers))] [InverseProperty(nameof(Tables.User.Markers))]
public virtual User User { get; set; } = null!; 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 public string RawAttachments
=> InternalRawAttachments(Id); => InternalRawAttachments(Id);
public static string InternalRawAttachments(string id)
=> throw new NotSupportedException();
[NotMapped] [Projectable] public bool IsPureRenote => (RenoteId != null || Renote != null) && !IsQuote; [NotMapped] [Projectable] public bool IsPureRenote => (RenoteId != null || Renote != null) && !IsQuote;
[NotMapped] [NotMapped]
@ -265,6 +262,9 @@ public class Note : IEntity
[StringLength(32)] [StringLength(32)]
public string Id { get; set; } = null!; public string Id { get; set; } = null!;
public static string InternalRawAttachments(string id)
=> throw new NotSupportedException();
[Projectable] [Projectable]
public bool TextContainsCaseInsensitive(string str) => public bool TextContainsCaseInsensitive(string str) =>
Text != null && EF.Functions.ILike(Text, "%" + EfHelpers.EscapeLikeQuery(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))] [InverseProperty(nameof(Tables.UserGroupInvitation.Notifications))]
public virtual UserGroupInvitation? UserGroupInvitation { get; set; } public virtual UserGroupInvitation? UserGroupInvitation { get; set; }
[Column("masto_id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long MastoId { get; set; }
[Key] [Key]
[Column("id")] [Column("id")]
[StringLength(32)] [StringLength(32)]
public string Id { get; set; } = null!; public string Id { get; set; } = null!;
[Column("masto_id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long MastoId { get; set; }
public Notification WithPrecomputedNoteVisibilities(bool reply, bool renote) public Notification WithPrecomputedNoteVisibilities(bool reply, bool renote)
{ {
Note = Note?.WithPrecomputedVisibilities(reply, renote); Note = Note?.WithPrecomputedVisibilities(reply, renote);

View file

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

View file

@ -494,6 +494,9 @@ public class User : IEntity
[NotMapped] public bool? PrecomputedIsRequested { get; set; } [NotMapped] public bool? PrecomputedIsRequested { get; set; }
[NotMapped] public bool? PrecomputedIsRequestedBy { get; set; } [NotMapped] public bool? PrecomputedIsRequestedBy { get; set; }
public bool IsLocalUser => Host == null;
public bool IsRemoteUser => Host != null;
[Key] [Key]
[Column("id")] [Column("id")]
[StringLength(32)] [StringLength(32)]
@ -616,7 +619,4 @@ public class User : IEntity
: throw new Exception("Cannot access PublicUrl for remote user"); : throw new Exception("Cannot access PublicUrl for remote user");
public string GetIdenticonUrl(string webDomain) => $"https://{webDomain}/identicon/{Id}"; 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 [PgName("private")] Private
} }
[Column("mentionsResolved")] public bool MentionsResolved;
[Key] [Key]
[Column("userId")] [Column("userId")]
[StringLength(32)] [StringLength(32)]
@ -176,9 +178,6 @@ public class UserProfile
[InverseProperty(nameof(Tables.User.UserProfile))] [InverseProperty(nameof(Tables.User.UserProfile))]
public virtual User User { get; set; } = null!; public virtual User User { get; set; } = null!;
[Column("mentionsResolved")]
public bool MentionsResolved;
public class Field public class Field
{ {
[J("name")] public required string Name { get; set; } [J("name")] public required string Name { get; set; }

View file

@ -28,7 +28,7 @@ public static class MvcBuilderExtensions
if (!opts.OutputFormatters.OfType<SystemTextJsonOutputFormatter>().Any()) if (!opts.OutputFormatters.OfType<SystemTextJsonOutputFormatter>().Any())
opts.OutputFormatters.Add(new SystemTextJsonOutputFormatter(jsonOpts.Value opts.OutputFormatters.Add(new SystemTextJsonOutputFormatter(jsonOpts.Value
.JsonSerializerOptions)); .JsonSerializerOptions));
opts.InputFormatters.Insert(0, new JsonInputMultiFormatter()); opts.InputFormatters.Insert(0, new JsonInputMultiFormatter());
opts.OutputFormatters.Insert(0, new JsonOutputMultiFormatter()); opts.OutputFormatters.Insert(0, new JsonOutputMultiFormatter());
@ -39,20 +39,19 @@ public static class MvcBuilderExtensions
public static IMvcBuilder AddModelBindingProviders(this IMvcBuilder builder) public static IMvcBuilder AddModelBindingProviders(this IMvcBuilder builder)
{ {
builder.Services.AddOptions<MvcOptions>().PostConfigure(options => builder.Services.AddOptions<MvcOptions>()
{ .PostConfigure(options => { options.ModelBinderProviders.AddHybridBindingProvider(); });
options.ModelBinderProviders.AddHybridBindingProvider();
});
return builder; return builder;
} }
public static IMvcBuilder AddValueProviderFactories(this IMvcBuilder 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()); {
}); options.ValueProviderFactories.Add(new JQueryQueryStringValueProviderFactory());
});
return builder; return builder;
} }

View file

@ -289,7 +289,9 @@ public static class QueryableExtensions
.Where(note => note.Reply == null || !note.Reply.User.IsMuting(user)); .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))); 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") }, Endpoints = new ASEndpoints { SharedInbox = new ASObjectBase($"https://{config.Value.WebDomain}/inbox") },
PublicKey = new ASPublicKey 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 Tags = tags
}; };

View file

@ -64,8 +64,6 @@ public static class LdHelpers
JToken.Parse(File.ReadAllText(Path.Combine("Core", "Federation", "ActivityStreams", "Contexts", JToken.Parse(File.ReadAllText(Path.Combine("Core", "Federation", "ActivityStreams", "Contexts",
"as-extensions.json"))); "as-extensions.json")));
private static IEnumerable<string> ASForceArray => ["tag", "to", "cc", "bcc", "bto"];
private static readonly JsonLdProcessorOptions Options = new() private static readonly JsonLdProcessorOptions Options = new()
{ {
DocumentLoader = CustomLoader, DocumentLoader = CustomLoader,
@ -75,7 +73,7 @@ public static class LdHelpers
ForceArray = ASForceArray.Select(p => $"{Constants.ActivityStreamsNs}#{p}").ToList(), ForceArray = ASForceArray.Select(p => $"{Constants.ActivityStreamsNs}#{p}").ToList(),
// separated for readability // separated for readability
RemoveUnusedInlineContextProperties = true, RemoveUnusedInlineContextProperties = true
}; };
public static readonly JsonSerializerSettings JsonSerializerSettings = new() public static readonly JsonSerializerSettings JsonSerializerSettings = new()
@ -88,6 +86,8 @@ public static class LdHelpers
NullValueHandling = NullValueHandling.Ignore, DateTimeZoneHandling = DateTimeZoneHandling.Local NullValueHandling = NullValueHandling.Ignore, DateTimeZoneHandling = DateTimeZoneHandling.Local
}; };
private static IEnumerable<string> ASForceArray => ["tag", "to", "cc", "bcc", "bto"];
private static RemoteDocument CustomLoader(Uri uri, JsonLdLoaderOptions jsonLdLoaderOptions) private static RemoteDocument CustomLoader(Uri uri, JsonLdLoaderOptions jsonLdLoaderOptions)
{ {
var key = uri.AbsolutePath == "/schemas/litepub-0.1.jsonld" ? "litepub-0.1" : uri.ToString(); 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 // Extensions
public const string Bite = "https://ns.mia.jetzt/as#Bite"; 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 class ASField : ASAttachment
{ {
public ASField() => Type = $"{Constants.SchemaNs}#PropertyValue";
[J($"{Constants.ActivityStreamsNs}#name")] [JC(typeof(ValueObjectConverter))] [J($"{Constants.ActivityStreamsNs}#name")] [JC(typeof(ValueObjectConverter))]
public string? Name; public string? Name;
[J($"{Constants.SchemaNs}#value")] [JC(typeof(ValueObjectConverter))] [J($"{Constants.SchemaNs}#value")] [JC(typeof(ValueObjectConverter))]
public string? Value; public string? Value;
public ASField() => Type = $"{Constants.SchemaNs}#PropertyValue";
} }
public sealed class ASAttachmentConverter : JsonConverter public sealed class ASAttachmentConverter : JsonConverter

View file

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

View file

@ -7,6 +7,8 @@ namespace Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
public class ASCollectionBase : ASObjectBase public class ASCollectionBase : ASObjectBase
{ {
public const string ObjectType = $"{Constants.ActivityStreamsNs}#Collection";
[J("@type")] [J("@type")]
[JC(typeof(StringListSingleConverter))] [JC(typeof(StringListSingleConverter))]
public string Type => ObjectType; public string Type => ObjectType;
@ -14,8 +16,6 @@ public class ASCollectionBase : ASObjectBase
[J($"{Constants.ActivityStreamsNs}#totalItems")] [J($"{Constants.ActivityStreamsNs}#totalItems")]
[JC(typeof(VC))] [JC(typeof(VC))]
public ulong? TotalItems { get; set; } public ulong? TotalItems { get; set; }
public const string ObjectType = $"{Constants.ActivityStreamsNs}#Collection";
} }
public sealed class ASCollectionBaseConverter : ASSerializer.ListSingleObjectConverter<ASCollectionBase>; 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 class ASCollectionPage : ASObject
{ {
public const string ObjectType = $"{Constants.ActivityStreamsNs}#CollectionPage";
[JsonConstructor] [JsonConstructor]
public ASCollectionPage(bool withType = true) => Type = withType ? ObjectType : null; public ASCollectionPage(bool withType = true) => Type = withType ? ObjectType : null;
@ -35,8 +37,6 @@ public class ASCollectionPage : ASObject
[J($"{Constants.ActivityStreamsNs}#next")] [J($"{Constants.ActivityStreamsNs}#next")]
[JC(typeof(ASLinkConverter))] [JC(typeof(ASLinkConverter))]
public ASLink? Next { get; set; } public ASLink? Next { get; set; }
public const string ObjectType = $"{Constants.ActivityStreamsNs}#CollectionPage";
} }
public sealed class ASCollectionPageConverter : JsonConverter public sealed class ASCollectionPageConverter : JsonConverter

View file

@ -94,13 +94,13 @@ public class ASNote : ASObject
{ {
if (actor.Host == null) throw new Exception("Can't get recipients for local actor"); if (actor.Host == null) throw new Exception("Can't get recipients for local actor");
return (To ?? []).Concat(Cc ?? []) return (To ?? []).Concat(Cc ?? [])
.Select(p => p.Id) .Select(p => p.Id)
.Distinct() .Distinct()
.Where(p => p != $"{Constants.ActivityStreamsNs}#Public" && .Where(p => p != $"{Constants.ActivityStreamsNs}#Public" &&
p != (actor.FollowersUri ?? actor.Uri + "/followers")) p != (actor.FollowersUri ?? actor.Uri + "/followers"))
.Where(p => p != null) .Where(p => p != null)
.Select(p => p!) .Select(p => p!)
.ToList(); .ToList();
} }
public new static class Types public new static class Types

View file

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

View file

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

View file

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

View file

@ -306,7 +306,9 @@ internal class MentionNodeParser : INodeParser
var node = new MfmMentionNode 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); return (node, chars);

View file

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

View file

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

View file

@ -191,7 +191,7 @@ public class NoteService(
} }
/// <remarks> /// <remarks>
/// This needs to be called before SaveChangesAsync on create & after on delete /// This needs to be called before SaveChangesAsync on create & after on delete
/// </remarks> /// </remarks>
private async Task UpdateNoteCountersAsync(Note note, bool create) private async Task UpdateNoteCountersAsync(Note note, bool create)
{ {

View file

@ -76,7 +76,12 @@ public class SystemUserService(ILogger<SystemUserService> logger, DatabaseContex
PublicKey = keypair.ExportSubjectPublicKeyInfoPem() 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() }; var usedUsername = new UsedUsername { CreatedAt = DateTime.UtcNow, Username = username.ToLowerInvariant() };

View file

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

View file

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

View file

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

View file

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