♻️ Optimization in file uploading
This commit is contained in:
parent
fa5c59a9c8
commit
d0a92bc8b3
@ -1,8 +1,10 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using DysonNetwork.Sphere.Storage;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using SystemClock = NodaTime.SystemClock;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Chat;
|
namespace DysonNetwork.Sphere.Chat;
|
||||||
|
|
||||||
@ -22,6 +24,8 @@ public partial class ChatController(AppDatabase db, ChatService cs) : Controller
|
|||||||
[MaxLength(36)] public string? Nonce { get; set; }
|
[MaxLength(36)] public string? Nonce { get; set; }
|
||||||
public List<string>? AttachmentsId { get; set; }
|
public List<string>? AttachmentsId { get; set; }
|
||||||
public Dictionary<string, object>? Meta { get; set; }
|
public Dictionary<string, object>? Meta { get; set; }
|
||||||
|
public Guid? RepliedMessageId { get; set; }
|
||||||
|
public Guid? ForwardedMessageId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{roomId:long}/messages")]
|
[HttpGet("{roomId:long}/messages")]
|
||||||
@ -52,8 +56,6 @@ public partial class ChatController(AppDatabase db, ChatService cs) : Controller
|
|||||||
.Include(m => m.Sender)
|
.Include(m => m.Sender)
|
||||||
.Include(m => m.Sender.Account)
|
.Include(m => m.Sender.Account)
|
||||||
.Include(m => m.Sender.Account.Profile)
|
.Include(m => m.Sender.Account.Profile)
|
||||||
.Include(m => m.Sender.Account.Profile.Picture)
|
|
||||||
.Include(m => m.Sender.Account.Profile.Background)
|
|
||||||
.Include(m => m.Attachments)
|
.Include(m => m.Attachments)
|
||||||
.Skip(offset)
|
.Skip(offset)
|
||||||
.Take(take)
|
.Take(take)
|
||||||
@ -63,8 +65,42 @@ public partial class ChatController(AppDatabase db, ChatService cs) : Controller
|
|||||||
|
|
||||||
return Ok(messages);
|
return Ok(messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("{roomId:long}/messages/{messageId:guid}")]
|
||||||
|
public async Task<ActionResult<Message>> GetMessage(long roomId, Guid messageId)
|
||||||
|
{
|
||||||
|
var currentUser = HttpContext.Items["CurrentUser"] as Account.Account;
|
||||||
|
|
||||||
|
var room = await db.ChatRooms.FirstOrDefaultAsync(r => r.Id == roomId);
|
||||||
|
if (room is null) return NotFound();
|
||||||
|
|
||||||
|
if (!room.IsPublic)
|
||||||
|
{
|
||||||
|
if (currentUser is null) return Unauthorized();
|
||||||
|
|
||||||
|
var member = await db.ChatMembers
|
||||||
|
.Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
if (member == null || member.Role < ChatMemberRole.Normal)
|
||||||
|
return StatusCode(403, "You are not a member of this chat room.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = await db.ChatMessages
|
||||||
|
.Where(m => m.Id == messageId && m.ChatRoomId == roomId)
|
||||||
|
.Include(m => m.Sender)
|
||||||
|
.Include(m => m.Sender.Account)
|
||||||
|
.Include(m => m.Sender.Account.Profile)
|
||||||
|
.Include(m => m.Attachments)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
if (message is null) return NotFound();
|
||||||
|
|
||||||
|
return Ok(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[GeneratedRegex(@"@([A-Za-z0-9_-]+)")]
|
[GeneratedRegex("@([A-Za-z0-9_-]+)")]
|
||||||
private static partial Regex MentionRegex();
|
private static partial Regex MentionRegex();
|
||||||
|
|
||||||
[HttpPost("{roomId:long}/messages")]
|
[HttpPost("{roomId:long}/messages")]
|
||||||
@ -102,6 +138,26 @@ public partial class ChatController(AppDatabase db, ChatService cs) : Controller
|
|||||||
.OrderBy(f => request.AttachmentsId.IndexOf(f.Id))
|
.OrderBy(f => request.AttachmentsId.IndexOf(f.Id))
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (request.RepliedMessageId.HasValue)
|
||||||
|
{
|
||||||
|
var repliedMessage = await db.ChatMessages
|
||||||
|
.FirstOrDefaultAsync(m => m.Id == request.RepliedMessageId.Value && m.ChatRoomId == roomId);
|
||||||
|
if (repliedMessage == null)
|
||||||
|
return BadRequest("The message you're replying to does not exist.");
|
||||||
|
|
||||||
|
message.RepliedMessageId = repliedMessage.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.ForwardedMessageId.HasValue)
|
||||||
|
{
|
||||||
|
var forwardedMessage = await db.ChatMessages
|
||||||
|
.FirstOrDefaultAsync(m => m.Id == request.ForwardedMessageId.Value);
|
||||||
|
if (forwardedMessage == null)
|
||||||
|
return BadRequest("The message you're forwarding does not exist.");
|
||||||
|
|
||||||
|
message.ForwardedMessageId = forwardedMessage.Id;
|
||||||
|
}
|
||||||
|
|
||||||
if (request.Content is not null)
|
if (request.Content is not null)
|
||||||
{
|
{
|
||||||
@ -123,14 +179,105 @@ public partial class ChatController(AppDatabase db, ChatService cs) : Controller
|
|||||||
|
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[HttpPatch("{roomId:long}/messages/{messageId:guid}")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<ActionResult> UpdateMessage([FromBody] SendMessageRequest request, long roomId, Guid messageId)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
|
var message = await db.ChatMessages
|
||||||
|
.Include(m => m.Sender)
|
||||||
|
.Include(m => m.Sender.Account)
|
||||||
|
.Include(m => m.Sender.Account.Profile)
|
||||||
|
.FirstOrDefaultAsync(m => m.Id == messageId && m.ChatRoomId == roomId);
|
||||||
|
if (message == null) return NotFound();
|
||||||
|
|
||||||
|
if (message.Sender.AccountId != currentUser.Id)
|
||||||
|
return StatusCode(403, "You can only edit your own messages.");
|
||||||
|
|
||||||
|
if (request.Content is not null)
|
||||||
|
message.Content = request.Content;
|
||||||
|
if (request.Meta is not null)
|
||||||
|
message.Meta = request.Meta;
|
||||||
|
|
||||||
|
if (request.RepliedMessageId.HasValue)
|
||||||
|
{
|
||||||
|
var repliedMessage = await db.ChatMessages
|
||||||
|
.FirstOrDefaultAsync(m => m.Id == request.RepliedMessageId.Value && m.ChatRoomId == roomId);
|
||||||
|
if (repliedMessage == null)
|
||||||
|
return BadRequest("The message you're replying to does not exist.");
|
||||||
|
|
||||||
|
message.RepliedMessageId = repliedMessage.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.ForwardedMessageId.HasValue)
|
||||||
|
{
|
||||||
|
var forwardedMessage = await db.ChatMessages
|
||||||
|
.FirstOrDefaultAsync(m => m.Id == request.ForwardedMessageId.Value);
|
||||||
|
if (forwardedMessage == null)
|
||||||
|
return BadRequest("The message you're forwarding does not exist.");
|
||||||
|
|
||||||
|
message.ForwardedMessageId = forwardedMessage.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.AttachmentsId is not null)
|
||||||
|
{
|
||||||
|
var records = await db.Files.Where(f => request.AttachmentsId.Contains(f.Id)).ToListAsync();
|
||||||
|
|
||||||
|
var previous = message.Attachments?.ToDictionary(f => f.Id) ?? new Dictionary<string, CloudFile>();
|
||||||
|
var current = records.ToDictionary(f => f.Id);
|
||||||
|
|
||||||
|
// Detect added files
|
||||||
|
var added = current.Keys.Except(previous.Keys).Select(id => current[id]).ToList();
|
||||||
|
// Detect removed files
|
||||||
|
var removed = previous.Keys.Except(current.Keys).Select(id => previous[id]).ToList();
|
||||||
|
|
||||||
|
// Update attachments
|
||||||
|
message.Attachments = request.AttachmentsId.Select(id => current[id]).ToList();
|
||||||
|
|
||||||
|
// Call mark usage
|
||||||
|
var fs = HttpContext.RequestServices.GetRequiredService<Storage.FileService>();
|
||||||
|
await fs.MarkUsageRangeAsync(added, 1);
|
||||||
|
await fs.MarkUsageRangeAsync(removed, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
message.EditedAt = SystemClock.Instance.GetCurrentInstant();
|
||||||
|
db.Update(message);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
return Ok(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{roomId:long}/messages/{messageId:guid}")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<ActionResult> DeleteMessage(long roomId, Guid messageId)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
|
var message = await db.ChatMessages
|
||||||
|
.Include(m => m.Sender)
|
||||||
|
.FirstOrDefaultAsync(m => m.Id == messageId && m.ChatRoomId == roomId);
|
||||||
|
if (message == null) return NotFound();
|
||||||
|
|
||||||
|
if (message.Sender.AccountId != currentUser.Id)
|
||||||
|
return StatusCode(403, "You can only delete your own messages.");
|
||||||
|
|
||||||
|
db.ChatMessages.Remove(message);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
public class SyncRequest
|
public class SyncRequest
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
public long LastSyncTimestamp { get; set; }
|
public long LastSyncTimestamp { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{roomId:long}/sync")]
|
[HttpPost("{roomId:long}/sync")]
|
||||||
public async Task<ActionResult<SyncResponse>> GetSyncData([FromBody] SyncRequest request, long roomId)
|
public async Task<ActionResult<SyncResponse>> GetSyncData([FromBody] SyncRequest request, long roomId)
|
||||||
{
|
{
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser)
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser)
|
||||||
|
@ -9,6 +9,9 @@ public class ChatService(AppDatabase db, IServiceScopeFactory scopeFactory)
|
|||||||
{
|
{
|
||||||
public async Task<Message> SendMessageAsync(Message message, ChatMember sender, ChatRoom room)
|
public async Task<Message> SendMessageAsync(Message message, ChatMember sender, ChatRoom room)
|
||||||
{
|
{
|
||||||
|
message.CreatedAt = SystemClock.Instance.GetCurrentInstant();
|
||||||
|
message.UpdatedAt = message.CreatedAt;
|
||||||
|
|
||||||
// First complete the save operation
|
// First complete the save operation
|
||||||
db.ChatMessages.Add(message);
|
db.ChatMessages.Add(message);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
@ -23,7 +26,7 @@ public class ChatService(AppDatabase db, IServiceScopeFactory scopeFactory)
|
|||||||
|
|
||||||
public async Task DeliverMessageAsync(Message message, ChatMember sender, ChatRoom room)
|
public async Task DeliverMessageAsync(Message message, ChatMember sender, ChatRoom room)
|
||||||
{
|
{
|
||||||
var scope = scopeFactory.CreateScope();
|
using var scope = scopeFactory.CreateScope();
|
||||||
var scopedDb = scope.ServiceProvider.GetRequiredService<AppDatabase>();
|
var scopedDb = scope.ServiceProvider.GetRequiredService<AppDatabase>();
|
||||||
var scopedWs = scope.ServiceProvider.GetRequiredService<WebSocketService>();
|
var scopedWs = scope.ServiceProvider.GetRequiredService<WebSocketService>();
|
||||||
var scopedNty = scope.ServiceProvider.GetRequiredService<NotificationService>();
|
var scopedNty = scope.ServiceProvider.GetRequiredService<NotificationService>();
|
||||||
@ -32,7 +35,7 @@ public class ChatService(AppDatabase db, IServiceScopeFactory scopeFactory)
|
|||||||
var tasks = new List<Task>();
|
var tasks = new List<Task>();
|
||||||
|
|
||||||
var members = await scopedDb.ChatMembers
|
var members = await scopedDb.ChatMembers
|
||||||
.Where(m => m.ChatRoomId == message.ChatRoomId)
|
.Where(m => m.ChatRoomId == message.ChatRoomId && m.AccountId != sender.AccountId)
|
||||||
.Where(m => m.Notify != ChatMemberNotify.None)
|
.Where(m => m.Notify != ChatMemberNotify.None)
|
||||||
.Where(m => m.Notify != ChatMemberNotify.Mentions ||
|
.Where(m => m.Notify != ChatMemberNotify.Mentions ||
|
||||||
(message.MembersMentioned != null && message.MembersMentioned.Contains(m.Id)))
|
(message.MembersMentioned != null && message.MembersMentioned.Contains(m.Id)))
|
||||||
@ -103,14 +106,17 @@ public class ChatService(AppDatabase db, IServiceScopeFactory scopeFactory)
|
|||||||
var timestamp = Instant.FromUnixTimeMilliseconds(lastSyncTimestamp);
|
var timestamp = Instant.FromUnixTimeMilliseconds(lastSyncTimestamp);
|
||||||
var changes = await db.ChatMessages
|
var changes = await db.ChatMessages
|
||||||
.IgnoreQueryFilters()
|
.IgnoreQueryFilters()
|
||||||
|
.Include(e => e.Sender)
|
||||||
|
.Include(e => e.Sender.Account)
|
||||||
|
.Include(e => e.Sender.Account.Profile)
|
||||||
.Where(m => m.ChatRoomId == roomId)
|
.Where(m => m.ChatRoomId == roomId)
|
||||||
.Where(m => m.UpdatedAt > timestamp || m.DeletedAt > timestamp)
|
.Where(m => m.UpdatedAt > timestamp || m.DeletedAt > timestamp)
|
||||||
.Select(m => new MessageChange
|
.Select(m => new MessageChange
|
||||||
{
|
{
|
||||||
MessageId = m.Id,
|
MessageId = m.Id,
|
||||||
Action = m.DeletedAt != null ? "delete" : (m.UpdatedAt == null ? "create" : "update"),
|
Action = m.DeletedAt != null ? "delete" : (m.UpdatedAt == m.CreatedAt ? "create" : "update"),
|
||||||
Message = m.DeletedAt != null ? null : m,
|
Message = m.DeletedAt != null ? null : m,
|
||||||
Timestamp = m.DeletedAt != null ? m.DeletedAt.Value : m.UpdatedAt
|
Timestamp = m.DeletedAt ?? m.UpdatedAt
|
||||||
})
|
})
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using NodaTime;
|
||||||
|
using NodaTime.Serialization.SystemTextJson;
|
||||||
|
|
||||||
public class WebSocketPacketType
|
public class WebSocketPacketType
|
||||||
{
|
{
|
||||||
@ -55,7 +57,7 @@ public class WebSocketPacket
|
|||||||
{
|
{
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||||
DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower,
|
DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||||
};
|
}.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);;
|
||||||
var json = JsonSerializer.Serialize(this, jsonOpts);
|
var json = JsonSerializer.Serialize(this, jsonOpts);
|
||||||
return System.Text.Encoding.UTF8.GetBytes(json);
|
return System.Text.Encoding.UTF8.GetBytes(json);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using DysonNetwork.Sphere.Chat;
|
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Connection;
|
namespace DysonNetwork.Sphere.Connection;
|
||||||
|
|
||||||
|
@ -28,6 +28,9 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Minio" Version="6.0.4" />
|
<PackageReference Include="Minio" Version="6.0.4" />
|
||||||
|
<PackageReference Include="NetVips" Version="3.0.1" />
|
||||||
|
<PackageReference Include="NetVips.Native.linux-x64" Version="8.16.1" />
|
||||||
|
<PackageReference Include="NetVips.Native.osx-arm64" Version="8.16.1" />
|
||||||
<PackageReference Include="NodaTime" Version="3.2.2" />
|
<PackageReference Include="NodaTime" Version="3.2.2" />
|
||||||
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" />
|
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||||
|
2359
DysonNetwork.Sphere/Migrations/20250503124624_InitialMigration.Designer.cs
generated
Normal file
2359
DysonNetwork.Sphere/Migrations/20250503124624_InitialMigration.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
1389
DysonNetwork.Sphere/Migrations/20250503124624_InitialMigration.cs
Normal file
1389
DysonNetwork.Sphere/Migrations/20250503124624_InitialMigration.cs
Normal file
File diff suppressed because it is too large
Load Diff
2356
DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs
Normal file
2356
DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,23 @@
|
|||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Permission;
|
namespace DysonNetwork.Sphere.Permission;
|
||||||
|
|
||||||
public class PermissionService(AppDatabase db)
|
public class PermissionService(
|
||||||
|
AppDatabase db,
|
||||||
|
IMemoryCache cache,
|
||||||
|
ILogger<PermissionService> logger)
|
||||||
{
|
{
|
||||||
|
private static readonly TimeSpan CacheExpiration = TimeSpan.FromMinutes(1);
|
||||||
|
|
||||||
|
private string GetPermissionCacheKey(string actor, string area, string key) =>
|
||||||
|
$"perm:{actor}:{area}:{key}";
|
||||||
|
|
||||||
|
private string GetGroupsCacheKey(string actor) =>
|
||||||
|
$"perm_groups:{actor}";
|
||||||
|
|
||||||
public async Task<bool> HasPermissionAsync(string actor, string area, string key)
|
public async Task<bool> HasPermissionAsync(string actor, string area, string key)
|
||||||
{
|
{
|
||||||
var value = await GetPermissionAsync<bool>(actor, area, key);
|
var value = await GetPermissionAsync<bool>(actor, area, key);
|
||||||
@ -14,13 +26,27 @@ public class PermissionService(AppDatabase db)
|
|||||||
|
|
||||||
public async Task<T?> GetPermissionAsync<T>(string actor, string area, string key)
|
public async Task<T?> GetPermissionAsync<T>(string actor, string area, string key)
|
||||||
{
|
{
|
||||||
|
var cacheKey = GetPermissionCacheKey(actor, area, key);
|
||||||
|
|
||||||
|
if (cache.TryGetValue<T>(cacheKey, out var cachedValue))
|
||||||
|
{
|
||||||
|
return cachedValue;
|
||||||
|
}
|
||||||
|
|
||||||
var now = SystemClock.Instance.GetCurrentInstant();
|
var now = SystemClock.Instance.GetCurrentInstant();
|
||||||
var groupsId = await db.PermissionGroupMembers
|
var groupsKey = GetGroupsCacheKey(actor);
|
||||||
.Where(n => n.Actor == actor)
|
|
||||||
.Where(n => n.ExpiredAt == null || n.ExpiredAt < now)
|
var groupsId = await cache.GetOrCreateAsync(groupsKey, async entry =>
|
||||||
.Where(n => n.AffectedAt == null || n.AffectedAt >= now)
|
{
|
||||||
.Select(e => e.GroupId)
|
entry.AbsoluteExpirationRelativeToNow = CacheExpiration;
|
||||||
.ToListAsync();
|
return await db.PermissionGroupMembers
|
||||||
|
.Where(n => n.Actor == actor)
|
||||||
|
.Where(n => n.ExpiredAt == null || n.ExpiredAt < now)
|
||||||
|
.Where(n => n.AffectedAt == null || n.AffectedAt >= now)
|
||||||
|
.Select(e => e.GroupId)
|
||||||
|
.ToListAsync();
|
||||||
|
});
|
||||||
|
|
||||||
var permission = await db.PermissionNodes
|
var permission = await db.PermissionNodes
|
||||||
.Where(n => n.GroupId == null || groupsId.Contains(n.GroupId.Value))
|
.Where(n => n.GroupId == null || groupsId.Contains(n.GroupId.Value))
|
||||||
.Where(n => (n.Key == key || n.Key == "*") && (n.GroupId != null || n.Actor == actor) && n.Area == area)
|
.Where(n => (n.Key == key || n.Key == "*") && (n.GroupId != null || n.Actor == actor) && n.Area == area)
|
||||||
@ -28,7 +54,11 @@ public class PermissionService(AppDatabase db)
|
|||||||
.Where(n => n.AffectedAt == null || n.AffectedAt >= now)
|
.Where(n => n.AffectedAt == null || n.AffectedAt >= now)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
return permission is not null ? _DeserializePermissionValue<T>(permission.Value) : default;
|
var result = permission is not null ? _DeserializePermissionValue<T>(permission.Value) : default;
|
||||||
|
|
||||||
|
cache.Set(cacheKey, result, CacheExpiration);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PermissionNode> AddPermissionNode<T>(
|
public async Task<PermissionNode> AddPermissionNode<T>(
|
||||||
@ -55,6 +85,9 @@ public class PermissionService(AppDatabase db)
|
|||||||
db.PermissionNodes.Add(node);
|
db.PermissionNodes.Add(node);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Invalidate related caches
|
||||||
|
InvalidatePermissionCache(actor, area, key);
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,6 +118,10 @@ public class PermissionService(AppDatabase db)
|
|||||||
db.PermissionNodes.Add(node);
|
db.PermissionNodes.Add(node);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Invalidate related caches
|
||||||
|
InvalidatePermissionCache(actor, area, key);
|
||||||
|
cache.Remove(GetGroupsCacheKey(actor));
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,6 +132,9 @@ public class PermissionService(AppDatabase db)
|
|||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
if (node is not null) db.PermissionNodes.Remove(node);
|
if (node is not null) db.PermissionNodes.Remove(node);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Invalidate cache
|
||||||
|
InvalidatePermissionCache(actor, area, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RemovePermissionNodeFromGroup<T>(PermissionGroup group, string actor, string area, string key)
|
public async Task RemovePermissionNodeFromGroup<T>(PermissionGroup group, string actor, string area, string key)
|
||||||
@ -106,6 +146,16 @@ public class PermissionService(AppDatabase db)
|
|||||||
if (node is null) return;
|
if (node is null) return;
|
||||||
db.PermissionNodes.Remove(node);
|
db.PermissionNodes.Remove(node);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Invalidate caches
|
||||||
|
InvalidatePermissionCache(actor, area, key);
|
||||||
|
cache.Remove(GetGroupsCacheKey(actor));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InvalidatePermissionCache(string actor, string area, string key)
|
||||||
|
{
|
||||||
|
var cacheKey = GetPermissionCacheKey(actor, area, key);
|
||||||
|
cache.Remove(cacheKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static T? _DeserializePermissionValue<T>(JsonDocument json)
|
private static T? _DeserializePermissionValue<T>(JsonDocument json)
|
||||||
|
@ -85,8 +85,6 @@ public class PostController(AppDatabase db, PostService ps, RelationshipService
|
|||||||
var posts = await db.Posts
|
var posts = await db.Posts
|
||||||
.Where(e => e.RepliedPostId == id)
|
.Where(e => e.RepliedPostId == id)
|
||||||
.Include(e => e.Publisher)
|
.Include(e => e.Publisher)
|
||||||
.Include(e => e.Publisher.Picture)
|
|
||||||
.Include(e => e.Publisher.Background)
|
|
||||||
.Include(e => e.ThreadedPost)
|
.Include(e => e.ThreadedPost)
|
||||||
.Include(e => e.ForwardedPost)
|
.Include(e => e.ForwardedPost)
|
||||||
.Include(e => e.Attachments)
|
.Include(e => e.Attachments)
|
||||||
|
@ -25,8 +25,10 @@ using NodaTime;
|
|||||||
using NodaTime.Serialization.SystemTextJson;
|
using NodaTime.Serialization.SystemTextJson;
|
||||||
using Quartz;
|
using Quartz;
|
||||||
using tusdotnet;
|
using tusdotnet;
|
||||||
|
using tusdotnet.Interfaces;
|
||||||
using tusdotnet.Models;
|
using tusdotnet.Models;
|
||||||
using tusdotnet.Models.Configuration;
|
using tusdotnet.Models.Configuration;
|
||||||
|
using tusdotnet.Stores;
|
||||||
using File = System.IO.File;
|
using File = System.IO.File;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
@ -116,6 +118,11 @@ builder.Services.AddSwaggerGen(options =>
|
|||||||
});
|
});
|
||||||
builder.Services.AddOpenApi();
|
builder.Services.AddOpenApi();
|
||||||
|
|
||||||
|
var tusDiskStore = new TusDiskStore(
|
||||||
|
builder.Configuration.GetSection("Tus").GetValue<string>("StorePath")!
|
||||||
|
);
|
||||||
|
builder.Services.AddSingleton(tusDiskStore);
|
||||||
|
|
||||||
// The handlers for websocket
|
// The handlers for websocket
|
||||||
builder.Services.AddScoped<IWebSocketPacketHandler, MessageReadHandler>();
|
builder.Services.AddScoped<IWebSocketPacketHandler, MessageReadHandler>();
|
||||||
|
|
||||||
@ -196,9 +203,6 @@ app.MapControllers().RequireRateLimiting("fixed");
|
|||||||
app.MapStaticAssets().RequireRateLimiting("fixed");
|
app.MapStaticAssets().RequireRateLimiting("fixed");
|
||||||
app.MapRazorPages().RequireRateLimiting("fixed");
|
app.MapRazorPages().RequireRateLimiting("fixed");
|
||||||
|
|
||||||
var tusDiskStore = new tusdotnet.Stores.TusDiskStore(
|
|
||||||
builder.Configuration.GetSection("Tus").GetValue<string>("StorePath")!
|
|
||||||
);
|
|
||||||
app.MapTus("/files/tus", _ => Task.FromResult<DefaultTusConfiguration>(new()
|
app.MapTus("/files/tus", _ => Task.FromResult<DefaultTusConfiguration>(new()
|
||||||
{
|
{
|
||||||
Store = tusDiskStore,
|
Store = tusDiskStore,
|
||||||
@ -216,8 +220,7 @@ app.MapTus("/files/tus", _ => Task.FromResult<DefaultTusConfiguration>(new()
|
|||||||
}
|
}
|
||||||
|
|
||||||
var httpContext = eventContext.HttpContext;
|
var httpContext = eventContext.HttpContext;
|
||||||
if (httpContext.Items["CurrentUser"] is Account user)
|
if (httpContext.Items["CurrentUser"] is not Account user)
|
||||||
if (user is null)
|
|
||||||
{
|
{
|
||||||
eventContext.FailRequest(HttpStatusCode.Unauthorized);
|
eventContext.FailRequest(HttpStatusCode.Unauthorized);
|
||||||
return;
|
return;
|
||||||
@ -226,7 +229,8 @@ app.MapTus("/files/tus", _ => Task.FromResult<DefaultTusConfiguration>(new()
|
|||||||
var userId = httpContext.User.FindFirst("user_id")?.Value;
|
var userId = httpContext.User.FindFirst("user_id")?.Value;
|
||||||
if (userId == null) return;
|
if (userId == null) return;
|
||||||
|
|
||||||
var pm = httpContext.RequestServices.GetRequiredService<PermissionService>();
|
using var scope = httpContext.RequestServices.CreateScope();
|
||||||
|
var pm = scope.ServiceProvider.GetRequiredService<PermissionService>();
|
||||||
var allowed = await pm.HasPermissionAsync($"user:{userId}", "global", "files.create");
|
var allowed = await pm.HasPermissionAsync($"user:{userId}", "global", "files.create");
|
||||||
if (!allowed)
|
if (!allowed)
|
||||||
{
|
{
|
||||||
@ -235,24 +239,37 @@ app.MapTus("/files/tus", _ => Task.FromResult<DefaultTusConfiguration>(new()
|
|||||||
},
|
},
|
||||||
OnFileCompleteAsync = async eventContext =>
|
OnFileCompleteAsync = async eventContext =>
|
||||||
{
|
{
|
||||||
var httpContext = eventContext.HttpContext;
|
using var scope = eventContext.HttpContext.RequestServices.CreateScope();
|
||||||
if (httpContext.Items["CurrentUser"] is not Account user) return;
|
var services = scope.ServiceProvider;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var httpContext = eventContext.HttpContext;
|
||||||
|
if (httpContext.Items["CurrentUser"] is not Account user) return;
|
||||||
|
|
||||||
var file = await eventContext.GetFileAsync();
|
var file = await eventContext.GetFileAsync();
|
||||||
var metadata = await file.GetMetadataAsync(eventContext.CancellationToken);
|
var metadata = await file.GetMetadataAsync(eventContext.CancellationToken);
|
||||||
var fileName = metadata.TryGetValue("filename", out var fn) ? fn.GetString(Encoding.UTF8) : "uploaded_file";
|
var fileName = metadata.TryGetValue("filename", out var fn) ? fn.GetString(Encoding.UTF8) : "uploaded_file";
|
||||||
var contentType = metadata.TryGetValue("content-type", out var ct) ? ct.GetString(Encoding.UTF8) : null;
|
var contentType = metadata.TryGetValue("content-type", out var ct) ? ct.GetString(Encoding.UTF8) : null;
|
||||||
var fileStream = await file.GetContentAsync(eventContext.CancellationToken);
|
|
||||||
|
var fileStream = await file.GetContentAsync(eventContext.CancellationToken);
|
||||||
|
|
||||||
|
var fileService = services.GetRequiredService<FileService>();
|
||||||
|
var info = await fileService.ProcessNewFileAsync(user, file.Id, fileStream, fileName, contentType);
|
||||||
|
|
||||||
var fileService = eventContext.HttpContext.RequestServices.GetRequiredService<FileService>();
|
using var finalScope = eventContext.HttpContext.RequestServices.CreateScope();
|
||||||
|
var jsonOptions = finalScope.ServiceProvider.GetRequiredService<IOptions<JsonOptions>>().Value.JsonSerializerOptions;
|
||||||
|
var infoJson = JsonSerializer.Serialize(info, jsonOptions);
|
||||||
|
eventContext.HttpContext.Response.Headers.Append("X-FileInfo", infoJson);
|
||||||
|
|
||||||
var info = await fileService.ProcessNewFileAsync(user, file.Id, fileStream, fileName, contentType);
|
// Dispose the stream after all processing is complete
|
||||||
|
await fileStream.DisposeAsync();
|
||||||
var jsonOptions = httpContext.RequestServices.GetRequiredService<IOptions<JsonOptions>>().Value
|
}
|
||||||
.JsonSerializerOptions;
|
catch (Exception ex)
|
||||||
var infoJson = JsonSerializer.Serialize(info, jsonOptions);
|
{
|
||||||
eventContext.HttpContext.Response.Headers.Append("X-FileInfo", infoJson);
|
throw;
|
||||||
},
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -4,17 +4,23 @@ using System.Security.Cryptography;
|
|||||||
using Blurhash.ImageSharp;
|
using Blurhash.ImageSharp;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Minio;
|
using Minio;
|
||||||
using Minio.DataModel;
|
|
||||||
using Minio.DataModel.Args;
|
using Minio.DataModel.Args;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using Quartz;
|
using Quartz;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
|
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
|
||||||
|
using tusdotnet.Stores;
|
||||||
using ExifTag = SixLabors.ImageSharp.Metadata.Profiles.Exif.ExifTag;
|
using ExifTag = SixLabors.ImageSharp.Metadata.Profiles.Exif.ExifTag;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Storage;
|
namespace DysonNetwork.Sphere.Storage;
|
||||||
|
|
||||||
public class FileService(AppDatabase db, IConfiguration configuration, ILogger<FileService> logger, IServiceScopeFactory scopeFactory)
|
public class FileService(
|
||||||
|
AppDatabase db,
|
||||||
|
IConfiguration configuration,
|
||||||
|
TusDiskStore store,
|
||||||
|
ILogger<FileService> logger,
|
||||||
|
IServiceScopeFactory scopeFactory
|
||||||
|
)
|
||||||
{
|
{
|
||||||
private static readonly string TempFilePrefix = "dyn-cloudfile";
|
private static readonly string TempFilePrefix = "dyn-cloudfile";
|
||||||
|
|
||||||
@ -28,17 +34,12 @@ public class FileService(AppDatabase db, IConfiguration configuration, ILogger<F
|
|||||||
string? contentType
|
string? contentType
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// If this variable present a value means the processor modified the uploaded file
|
var result = new List<(string filePath, string suffix)>();
|
||||||
// Upload this file to the remote instead
|
|
||||||
var modifiedResult = new List<(string filePath, string suffix)>();
|
|
||||||
|
|
||||||
var fileSize = stream.Length;
|
var fileSize = stream.Length;
|
||||||
var hash = await HashFileAsync(stream, fileSize: fileSize);
|
var hash = await HashFileAsync(stream, fileSize: fileSize);
|
||||||
contentType ??= !fileName.Contains('.') ? "application/octet-stream" : MimeTypes.GetMimeType(fileName);
|
contentType ??= !fileName.Contains('.') ? "application/octet-stream" : MimeTypes.GetMimeType(fileName);
|
||||||
|
|
||||||
var existingFile = await db.Files.Where(f => f.Hash == hash).FirstOrDefaultAsync();
|
|
||||||
if (existingFile is not null) return existingFile;
|
|
||||||
|
|
||||||
var file = new CloudFile
|
var file = new CloudFile
|
||||||
{
|
{
|
||||||
Id = fileId,
|
Id = fileId,
|
||||||
@ -53,17 +54,28 @@ public class FileService(AppDatabase db, IConfiguration configuration, ILogger<F
|
|||||||
{
|
{
|
||||||
case "image":
|
case "image":
|
||||||
stream.Position = 0;
|
stream.Position = 0;
|
||||||
using (var imageSharp = await Image.LoadAsync<Rgba32>(stream))
|
// We still need ImageSharp for blurhash calculation
|
||||||
|
using (var imageSharp = await SixLabors.ImageSharp.Image.LoadAsync<Rgba32>(stream))
|
||||||
{
|
{
|
||||||
var width = imageSharp.Width;
|
|
||||||
var height = imageSharp.Height;
|
|
||||||
var blurhash = Blurhasher.Encode(imageSharp, 3, 3);
|
var blurhash = Blurhasher.Encode(imageSharp, 3, 3);
|
||||||
var format = imageSharp.Metadata.DecodedImageFormat?.Name ?? "unknown";
|
|
||||||
|
|
||||||
var exifProfile = imageSharp.Metadata.ExifProfile;
|
// Reset stream position after ImageSharp read
|
||||||
|
stream.Position = 0;
|
||||||
|
|
||||||
|
// Use NetVips for the rest
|
||||||
|
using var vipsImage = NetVips.Image.NewFromStream(stream);
|
||||||
|
|
||||||
|
var width = vipsImage.Width;
|
||||||
|
var height = vipsImage.Height;
|
||||||
|
var format = vipsImage.Get("vips-loader") ?? "unknown";
|
||||||
|
|
||||||
|
// Try to get orientation from exif data
|
||||||
ushort orientation = 1;
|
ushort orientation = 1;
|
||||||
List<IExifValue> exif = [];
|
List<IExifValue> exif = [];
|
||||||
|
|
||||||
|
// NetVips supports reading exif with vipsImage.GetField("exif-ifd0-Orientation")
|
||||||
|
// but we'll keep the ImageSharp exif handling for now
|
||||||
|
var exifProfile = imageSharp.Metadata.ExifProfile;
|
||||||
if (exifProfile?.Values.FirstOrDefault(e => e.Tag == ExifTag.Orientation)
|
if (exifProfile?.Values.FirstOrDefault(e => e.Tag == ExifTag.Orientation)
|
||||||
?.GetValue() is ushort o)
|
?.GetValue() is ushort o)
|
||||||
orientation = o;
|
orientation = o;
|
||||||
@ -117,7 +129,7 @@ public class FileService(AppDatabase db, IConfiguration configuration, ILogger<F
|
|||||||
{
|
{
|
||||||
using var scope = scopeFactory.CreateScope();
|
using var scope = scopeFactory.CreateScope();
|
||||||
var nfs = scope.ServiceProvider.GetRequiredService<FileService>();
|
var nfs = scope.ServiceProvider.GetRequiredService<FileService>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
logger.LogInformation("Processed file {fileId}, now trying optimizing if possible...", fileId);
|
logger.LogInformation("Processed file {fileId}, now trying optimizing if possible...", fileId);
|
||||||
@ -125,40 +137,53 @@ public class FileService(AppDatabase db, IConfiguration configuration, ILogger<F
|
|||||||
if (contentType.Split('/')[0] == "image")
|
if (contentType.Split('/')[0] == "image")
|
||||||
{
|
{
|
||||||
file.MimeType = "image/webp";
|
file.MimeType = "image/webp";
|
||||||
|
|
||||||
List<Task> tasks = [];
|
List<Task> tasks = [];
|
||||||
|
|
||||||
var ogFilePath = Path.Join(configuration.GetValue<string>("Tus:StorePath"), file.Id);
|
var ogFilePath = Path.Join(configuration.GetValue<string>("Tus:StorePath"), file.Id);
|
||||||
using var imageSharp = await Image.LoadAsync<Rgba32>(ogFilePath);
|
var vipsImage = NetVips.Image.NewFromFile(ogFilePath);
|
||||||
var imagePath = Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{file.Id}");
|
var imagePath = Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{file.Id}");
|
||||||
tasks.Add(imageSharp.SaveAsWebpAsync(imagePath));
|
tasks.Add(Task.Run(() => vipsImage.WriteToFile(imagePath + ".webp")));
|
||||||
modifiedResult.Add((imagePath, string.Empty));
|
result.Add((imagePath + ".webp", string.Empty));
|
||||||
|
|
||||||
if (imageSharp.Size.Width * imageSharp.Size.Height >= 1024 * 1024)
|
if (vipsImage.Width * vipsImage.Height >= 1024 * 1024)
|
||||||
{
|
{
|
||||||
var compressedClone = imageSharp.Clone();
|
var scale = 1024.0 / Math.Max(vipsImage.Width, vipsImage.Height);
|
||||||
compressedClone.Mutate(i => i.Resize(new ResizeOptions
|
|
||||||
{
|
|
||||||
Mode = ResizeMode.Max,
|
|
||||||
Size = new Size(1024, 1024),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
var imageCompressedPath =
|
var imageCompressedPath =
|
||||||
Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{file.Id}-compressed");
|
Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{file.Id}-compressed");
|
||||||
tasks.Add(compressedClone.SaveAsWebpAsync(imageCompressedPath));
|
|
||||||
modifiedResult.Add((imageCompressedPath, ".compressed"));
|
// Create and save image within the same synchronous block to avoid disposal issues
|
||||||
|
tasks.Add(Task.Run(() => {
|
||||||
|
using var compressedImage = vipsImage.Resize(scale);
|
||||||
|
compressedImage.WriteToFile(imageCompressedPath + ".webp");
|
||||||
|
vipsImage.Dispose();
|
||||||
|
}));
|
||||||
|
|
||||||
|
result.Add((imageCompressedPath + ".webp", ".compressed"));
|
||||||
file.HasCompression = true;
|
file.HasCompression = true;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vipsImage.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var tempFilePath = Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{file.Id}");
|
||||||
|
await using var fileStream = File.Create(tempFilePath);
|
||||||
|
stream.Position = 0;
|
||||||
|
await stream.CopyToAsync(fileStream);
|
||||||
|
result.Add((tempFilePath, string.Empty));
|
||||||
|
}
|
||||||
|
|
||||||
logger.LogInformation("Optimized file {fileId}, now uploading...", fileId);
|
logger.LogInformation("Optimized file {fileId}, now uploading...", fileId);
|
||||||
|
|
||||||
if (modifiedResult.Count > 0)
|
if (result.Count > 0)
|
||||||
{
|
{
|
||||||
List<Task<CloudFile>> tasks = [];
|
List<Task<CloudFile>> tasks = [];
|
||||||
tasks.AddRange(modifiedResult.Select(result =>
|
tasks.AddRange(result.Select(result =>
|
||||||
nfs.UploadFileToRemoteAsync(file, result.filePath, null, result.suffix, true)));
|
nfs.UploadFileToRemoteAsync(file, result.filePath, null, result.suffix, true)));
|
||||||
|
|
||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
@ -183,7 +208,10 @@ public class FileService(AppDatabase db, IConfiguration configuration, ILogger<F
|
|||||||
{
|
{
|
||||||
logger.LogError(err, "Failed to process {fileId}", fileId);
|
logger.LogError(err, "Failed to process {fileId}", fileId);
|
||||||
}
|
}
|
||||||
}).ConfigureAwait(false);
|
|
||||||
|
await stream.DisposeAsync();
|
||||||
|
await store.DeleteFileAsync(file.Id, CancellationToken.None);
|
||||||
|
});
|
||||||
|
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
@ -250,7 +278,7 @@ public class FileService(AppDatabase db, IConfiguration configuration, ILogger<F
|
|||||||
await client.PutObjectAsync(new PutObjectArgs()
|
await client.PutObjectAsync(new PutObjectArgs()
|
||||||
.WithBucket(bucket)
|
.WithBucket(bucket)
|
||||||
.WithObject(string.IsNullOrWhiteSpace(suffix) ? file.Id : file.Id + suffix)
|
.WithObject(string.IsNullOrWhiteSpace(suffix) ? file.Id : file.Id + suffix)
|
||||||
.WithStreamData(stream)
|
.WithStreamData(stream) // Fix this disposed
|
||||||
.WithObjectSize(stream.Length)
|
.WithObjectSize(stream.Length)
|
||||||
.WithContentType(contentType)
|
.WithContentType(contentType)
|
||||||
);
|
);
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True"
|
"App": "Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60"
|
||||||
},
|
},
|
||||||
"Authentication": {
|
"Authentication": {
|
||||||
"Schemes": {
|
"Schemes": {
|
||||||
|
@ -19,11 +19,14 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEntityFrameworkServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F4a28847852ee9ba45fd3107526c0a749a733bd4f4ebf33aa3c9a59737a3f758_003FEntityFrameworkServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEntityFrameworkServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F4a28847852ee9ba45fd3107526c0a749a733bd4f4ebf33aa3c9a59737a3f758_003FEntityFrameworkServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnumerable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F832399abc13b45b6bdbabfa022e4a28487e00_003F7f_003F7aece4dd_003FEnumerable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnumerable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F832399abc13b45b6bdbabfa022e4a28487e00_003F7f_003F7aece4dd_003FEnumerable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEvents_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003F20_003F86914b63_003FEvents_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEvents_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003F20_003F86914b63_003FEvents_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003Fbf_003F44af6d95_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExifTag_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa932cb9090ed48088111ae919dcdd9021ba00_003Fd7_003F0472c800_003FExifTag_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExifTag_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa932cb9090ed48088111ae919dcdd9021ba00_003Fd7_003F0472c800_003FExifTag_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExifTag_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fef3339e864a448e2b1ec6fa7bbf4c6661fee00_003F5c_003F8ed75f18_003FExifTag_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExifTag_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fef3339e864a448e2b1ec6fa7bbf4c6661fee00_003F5c_003F8ed75f18_003FExifTag_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFileResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F8c_003F9f6e3f4f_003FFileResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFileResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F8c_003F9f6e3f4f_003FFileResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AForwardedHeaders_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcfe5737f9bb84738979cbfedd11822a8ea00_003F50_003F9a335f87_003FForwardedHeaders_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AForwardedHeaders_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcfe5737f9bb84738979cbfedd11822a8ea00_003F50_003F9a335f87_003FForwardedHeaders_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIConfiguration_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fbb55221b2bd14b31a20b0d8bdcc7ff457328_003F19_003F707d23be_003FIConfiguration_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AImageFile_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa932cb9090ed48088111ae919dcdd9021ba00_003F71_003F0a804432_003FImageFile_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AImageFile_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa932cb9090ed48088111ae919dcdd9021ba00_003F71_003F0a804432_003FImageFile_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AImage_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fdaa8d9c408cd4b4286bbef7e35f1a42e31c00_003F9f_003Fc5bde8be_003FImage_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIndexAttribute_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe38f14ac86274ebb9b366729231d1c1a8838_003F8b_003F2890293d_003FIndexAttribute_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIndexAttribute_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe38f14ac86274ebb9b366729231d1c1a8838_003F8b_003F2890293d_003FIndexAttribute_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIntentType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fbf_003Ffcb84131_003FIntentType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIntentType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fbf_003Ffcb84131_003FIntentType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIServiceCollectionQuartzConfigurator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1edbd6e24d7b430fabce72177269baa19200_003F67_003Faee36f5b_003FIServiceCollectionQuartzConfigurator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIServiceCollectionQuartzConfigurator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1edbd6e24d7b430fabce72177269baa19200_003F67_003Faee36f5b_003FIServiceCollectionQuartzConfigurator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
@ -40,13 +43,18 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOptionsConfigurationServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6622dea924b14dc7aa3ee69d7c84e5735000_003Fe0_003F024ba0b7_003FOptionsConfigurationServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOptionsConfigurationServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6622dea924b14dc7aa3ee69d7c84e5735000_003Fe0_003F024ba0b7_003FOptionsConfigurationServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APath_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003Fd3_003F7b05b2bd_003FPath_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APath_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003Fd3_003F7b05b2bd_003FPath_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APresignedGetObjectArgs_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F0df26a9d89e29319e9efcaea0a8489db9e97bc1aedcca3f7e360cc50f8f4ea_003FPresignedGetObjectArgs_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APresignedGetObjectArgs_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F0df26a9d89e29319e9efcaea0a8489db9e97bc1aedcca3f7e360cc50f8f4ea_003FPresignedGetObjectArgs_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APutObjectArgs_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F6efe388c7585d5dd5587416a55298550b030c2a107edf45f988791297c3ffa_003FPutObjectArgs_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AQueryable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F42d8f09d6a294d00a6f49efc989927492fe00_003F4e_003F26d1ee34_003FQueryable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AQueryable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F42d8f09d6a294d00a6f49efc989927492fe00_003F4e_003F26d1ee34_003FQueryable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AResizeOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fef3339e864a448e2b1ec6fa7bbf4c6661fee00_003F48_003F0209e410_003FResizeOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AResizeOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fef3339e864a448e2b1ec6fa7bbf4c6661fee00_003F48_003F0209e410_003FResizeOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASafeHandle_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003F66_003Fde27c365_003FSafeHandle_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASecuritySchemeType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F29898ce74e3763a786ac1bd9a6db2152e1af75769440b1e53b9cbdf1dda1bd99_003FSecuritySchemeType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASecuritySchemeType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F29898ce74e3763a786ac1bd9a6db2152e1af75769440b1e53b9cbdf1dda1bd99_003FSecuritySchemeType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollectionContainerBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc0e30e11d8f5456cb7a11b21ebee6c5a35c00_003F60_003F78b485f5_003FServiceCollectionContainerBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollectionContainerBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc0e30e11d8f5456cb7a11b21ebee6c5a35c00_003F60_003F78b485f5_003FServiceCollectionContainerBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASetPropertyCalls_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F458b5f22476b4599b87176214d5e4026c2327b148f4d3f885ee92362b4dac3_003FSetPropertyCalls_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASetPropertyCalls_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F458b5f22476b4599b87176214d5e4026c2327b148f4d3f885ee92362b4dac3_003FSetPropertyCalls_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASourceCustom_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fdaa8d9c408cd4b4286bbef7e35f1a42e31c00_003F45_003F5839ca6c_003FSourceCustom_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStatusCodeResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F7c_003F8b7572ae_003FStatusCodeResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStatusCodeResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F7c_003F8b7572ae_003FStatusCodeResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATagging_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F36f4c2e6baa65ba603de42eedad12ea36845aa35a910a6a82d82baf688e3e1_003FTagging_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATagging_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F36f4c2e6baa65ba603de42eedad12ea36845aa35a910a6a82d82baf688e3e1_003FTagging_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003F12_003Fe0a28ad6_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATusDiskStore_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fe1_003Fefd9af34_003FTusDiskStore_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATusDiskStore_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fe1_003Fefd9af34_003FTusDiskStore_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATusDiskStore_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003F1c_003F21999acd_003FTusDiskStore_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUri_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5d2c480da9be415dab9be535bb6d08713cc00_003Fd0_003Fffc36a51_003FUri_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUri_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5d2c480da9be415dab9be535bb6d08713cc00_003Fd0_003Fffc36a51_003FUri_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AValidationContext_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003F6b_003F741ceebe_003FValidationContext_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AValidationContext_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003F6b_003F741ceebe_003FValidationContext_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
Loading…
x
Reference in New Issue
Block a user