Compare commits
3 Commits
205ccd66b3
...
18fde9f16c
| Author | SHA1 | Date | |
|---|---|---|---|
| 18fde9f16c | |||
| 4e794ceb9b | |||
| b40282e43a |
@@ -1,11 +1,12 @@
|
|||||||
using DysonNetwork.Sphere.Account;
|
using DysonNetwork.Sphere.Account;
|
||||||
using DysonNetwork.Sphere.Connection;
|
using DysonNetwork.Sphere.Connection;
|
||||||
|
using DysonNetwork.Sphere.Storage;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Chat;
|
namespace DysonNetwork.Sphere.Chat;
|
||||||
|
|
||||||
public class ChatService(AppDatabase db, IServiceScopeFactory scopeFactory)
|
public class ChatService(AppDatabase db, FileService fs, IServiceScopeFactory scopeFactory)
|
||||||
{
|
{
|
||||||
public async Task<Message> SendMessageAsync(Message message, ChatMember sender, ChatRoom room)
|
public async Task<Message> SendMessageAsync(Message message, ChatMember sender, ChatRoom room)
|
||||||
{
|
{
|
||||||
@@ -16,6 +17,9 @@ public class ChatService(AppDatabase db, IServiceScopeFactory scopeFactory)
|
|||||||
db.ChatMessages.Add(message);
|
db.ChatMessages.Add(message);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
var files = message.Attachments.Distinct().ToList();
|
||||||
|
if (files.Count != 0) await fs.MarkUsageRangeAsync(files, 1);
|
||||||
|
|
||||||
// Then start the delivery process
|
// Then start the delivery process
|
||||||
// Using ConfigureAwait(false) is correct here since we don't need context to flow
|
// Using ConfigureAwait(false) is correct here since we don't need context to flow
|
||||||
_ = Task.Run(() => DeliverMessageAsync(message, sender, room))
|
_ = Task.Run(() => DeliverMessageAsync(message, sender, room))
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
using System.Text.Json.Serialization;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using tencentyun;
|
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Chat;
|
namespace DysonNetwork.Sphere.Chat;
|
||||||
|
|
||||||
public class RealtimeChatConfiguration
|
public class RealtimeChatConfiguration
|
||||||
{
|
{
|
||||||
public string Provider { get; set; } = null!;
|
public string Provider { get; set; } = null!;
|
||||||
public int AppId { get; set; }
|
|
||||||
[JsonIgnore] public string SecretKey { get; set; } = null!;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
@@ -20,46 +16,6 @@ public class RealtimeCallController(IConfiguration configuration, AppDatabase db
|
|||||||
private readonly RealtimeChatConfiguration _config =
|
private readonly RealtimeChatConfiguration _config =
|
||||||
configuration.GetSection("RealtimeChat").Get<RealtimeChatConfiguration>()!;
|
configuration.GetSection("RealtimeChat").Get<RealtimeChatConfiguration>()!;
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
public ActionResult<RealtimeChatConfiguration> GetConfiguration()
|
|
||||||
{
|
|
||||||
return _config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RealtimeChatToken
|
|
||||||
{
|
|
||||||
public RealtimeChatConfiguration Config { get; set; } = null!;
|
|
||||||
public string Token { get; set; } = null!;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{roomId:guid}")]
|
|
||||||
[Authorize]
|
|
||||||
public async Task<ActionResult<RealtimeChatToken>> GetToken(Guid roomId)
|
|
||||||
{
|
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
|
||||||
|
|
||||||
var member = await db.ChatMembers
|
|
||||||
.Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
if (member == null || member.Role < ChatMemberRole.Member)
|
|
||||||
return StatusCode(403,
|
|
||||||
"You need to be a normal member to get the token for joining the realtime chatroom."
|
|
||||||
);
|
|
||||||
|
|
||||||
var ongoingCall = await cs.GetCallOngoingAsync(roomId);
|
|
||||||
if (ongoingCall is null) return BadRequest("No ongoing call.");
|
|
||||||
|
|
||||||
var api = new TLSSigAPIv2(_config.AppId, _config.SecretKey);
|
|
||||||
var sig = api.GenSig(currentUser.Name);
|
|
||||||
if (sig is null) return StatusCode(500, "Failed to generate the token.");
|
|
||||||
|
|
||||||
return Ok(new RealtimeChatToken
|
|
||||||
{
|
|
||||||
Config = _config,
|
|
||||||
Token = sig
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("{roomId:guid}")]
|
[HttpPost("{roomId:guid}")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<IActionResult> StartCall(Guid roomId)
|
public async Task<IActionResult> StartCall(Guid roomId)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||||
<PackageReference Include="Blurhash.ImageSharp" Version="4.0.0" />
|
<PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.3.4" />
|
||||||
<PackageReference Include="CorePush" Version="4.3.0" />
|
<PackageReference Include="CorePush" Version="4.3.0" />
|
||||||
<PackageReference Include="EFCore.BulkExtensions" Version="9.0.1" />
|
<PackageReference Include="EFCore.BulkExtensions" Version="9.0.1" />
|
||||||
<PackageReference Include="EFCore.BulkExtensions.PostgreSql" Version="9.0.1" />
|
<PackageReference Include="EFCore.BulkExtensions.PostgreSql" Version="9.0.1" />
|
||||||
@@ -58,12 +58,8 @@
|
|||||||
<PackageReference Include="Quartz" Version="3.14.0" />
|
<PackageReference Include="Quartz" Version="3.14.0" />
|
||||||
<PackageReference Include="Quartz.AspNetCore" Version="3.14.0" />
|
<PackageReference Include="Quartz.AspNetCore" Version="3.14.0" />
|
||||||
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.14.0" />
|
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.14.0" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" />
|
|
||||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.5" />
|
|
||||||
<PackageReference Include="SixLabors.ImageSharp.Web" Version="3.1.4" />
|
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="8.1.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="8.1.0" />
|
||||||
<PackageReference Include="tls-sig-api-v2" Version="1.0.1" />
|
|
||||||
<PackageReference Include="tusdotnet" Version="2.8.1" />
|
<PackageReference Include="tusdotnet" Version="2.8.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
3386
DysonNetwork.Sphere/Migrations/20250518041519_OptimizeDataStructure.Designer.cs
generated
Normal file
3386
DysonNetwork.Sphere/Migrations/20250518041519_OptimizeDataStructure.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,103 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class OptimizeDataStructure : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "chat_statuses");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "chat_read_receipts",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
message_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
sender_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_chat_read_receipts", x => new { x.message_id, x.sender_id });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_chat_read_receipts_chat_members_sender_id",
|
||||||
|
column: x => x.sender_id,
|
||||||
|
principalTable: "chat_members",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_chat_read_receipts_chat_messages_message_id",
|
||||||
|
column: x => x.message_id,
|
||||||
|
principalTable: "chat_messages",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_chat_read_receipts_message_id_sender_id",
|
||||||
|
table: "chat_read_receipts",
|
||||||
|
columns: new[] { "message_id", "sender_id" },
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_chat_read_receipts_sender_id",
|
||||||
|
table: "chat_read_receipts",
|
||||||
|
column: "sender_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "chat_read_receipts");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "chat_statuses",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
message_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
sender_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||||
|
read_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_chat_statuses", x => new { x.message_id, x.sender_id });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_chat_statuses_chat_members_sender_id",
|
||||||
|
column: x => x.sender_id,
|
||||||
|
principalTable: "chat_members",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_chat_statuses_chat_messages_message_id",
|
||||||
|
column: x => x.message_id,
|
||||||
|
principalTable: "chat_messages",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_chat_statuses_message_id_sender_id",
|
||||||
|
table: "chat_statuses",
|
||||||
|
columns: new[] { "message_id", "sender_id" },
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_chat_statuses_sender_id",
|
||||||
|
table: "chat_statuses",
|
||||||
|
column: "sender_id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1143,7 +1143,7 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.ToTable("chat_reactions", (string)null);
|
b.ToTable("chat_reactions", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageStatus", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReadReceipt", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("MessageId")
|
b.Property<Guid>("MessageId")
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
@@ -1161,25 +1161,21 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("deleted_at");
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
b.Property<Instant>("ReadAt")
|
|
||||||
.HasColumnType("timestamp with time zone")
|
|
||||||
.HasColumnName("read_at");
|
|
||||||
|
|
||||||
b.Property<Instant>("UpdatedAt")
|
b.Property<Instant>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("updated_at");
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
b.HasKey("MessageId", "SenderId")
|
b.HasKey("MessageId", "SenderId")
|
||||||
.HasName("pk_chat_statuses");
|
.HasName("pk_chat_read_receipts");
|
||||||
|
|
||||||
b.HasIndex("SenderId")
|
b.HasIndex("SenderId")
|
||||||
.HasDatabaseName("ix_chat_statuses_sender_id");
|
.HasDatabaseName("ix_chat_read_receipts_sender_id");
|
||||||
|
|
||||||
b.HasIndex("MessageId", "SenderId")
|
b.HasIndex("MessageId", "SenderId")
|
||||||
.IsUnique()
|
.IsUnique()
|
||||||
.HasDatabaseName("ix_chat_statuses_message_id_sender_id");
|
.HasDatabaseName("ix_chat_read_receipts_message_id_sender_id");
|
||||||
|
|
||||||
b.ToTable("chat_statuses", (string)null);
|
b.ToTable("chat_read_receipts", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Chat.RealtimeCall", b =>
|
||||||
@@ -2850,21 +2846,21 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.Navigation("Sender");
|
b.Navigation("Sender");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageStatus", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReadReceipt", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message")
|
b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message")
|
||||||
.WithMany("Statuses")
|
.WithMany("Statuses")
|
||||||
.HasForeignKey("MessageId")
|
.HasForeignKey("MessageId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasConstraintName("fk_chat_statuses_chat_messages_message_id");
|
.HasConstraintName("fk_chat_read_receipts_chat_messages_message_id");
|
||||||
|
|
||||||
b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender")
|
b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("SenderId")
|
.HasForeignKey("SenderId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasConstraintName("fk_chat_statuses_chat_members_sender_id");
|
.HasConstraintName("fk_chat_read_receipts_chat_members_sender_id");
|
||||||
|
|
||||||
b.Navigation("Message");
|
b.Navigation("Message");
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using FFMpegCore;
|
using FFMpegCore;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using Blurhash.ImageSharp;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Minio;
|
using Minio;
|
||||||
using Minio.DataModel.Args;
|
using Minio.DataModel.Args;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using Quartz;
|
using Quartz;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
|
||||||
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
|
|
||||||
using tusdotnet.Stores;
|
using tusdotnet.Stores;
|
||||||
using ExifTag = SixLabors.ImageSharp.Metadata.Profiles.Exif.ExifTag;
|
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Storage;
|
namespace DysonNetwork.Sphere.Storage;
|
||||||
|
|
||||||
@@ -36,6 +32,7 @@ public class FileService(
|
|||||||
{
|
{
|
||||||
var result = new List<(string filePath, string suffix)>();
|
var result = new List<(string filePath, string suffix)>();
|
||||||
|
|
||||||
|
var ogFilePath = Path.Join(configuration.GetValue<string>("Tus:StorePath"), fileId);
|
||||||
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);
|
||||||
@@ -53,32 +50,28 @@ public class FileService(
|
|||||||
switch (contentType.Split('/')[0])
|
switch (contentType.Split('/')[0])
|
||||||
{
|
{
|
||||||
case "image":
|
case "image":
|
||||||
|
var blurhash = BlurHashSharp.SkiaSharp.BlurHashEncoder.Encode(xComponent: 3, yComponent: 3, filename: ogFilePath);
|
||||||
|
|
||||||
|
// Rewind stream
|
||||||
stream.Position = 0;
|
stream.Position = 0;
|
||||||
// We still need ImageSharp for blurhash calculation
|
|
||||||
using (var imageSharp = await Image.LoadAsync<Rgba32>(stream))
|
// Use NetVips for the rest
|
||||||
|
using (var vipsImage = NetVips.Image.NewFromStream(stream))
|
||||||
{
|
{
|
||||||
var blurhash = Blurhasher.Encode(imageSharp, 3, 3);
|
|
||||||
|
|
||||||
// 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 width = vipsImage.Width;
|
||||||
var height = vipsImage.Height;
|
var height = vipsImage.Height;
|
||||||
var format = vipsImage.Get("vips-loader") ?? "unknown";
|
var format = vipsImage.Get("vips-loader") ?? "unknown";
|
||||||
|
|
||||||
// Try to get orientation from exif data
|
// Try to get orientation from exif data
|
||||||
ushort orientation = 1;
|
int orientation = 1;
|
||||||
List<IExifValue> exif = [];
|
Dictionary<string, object> exif = [];
|
||||||
|
|
||||||
// NetVips supports reading exif with vipsImage.GetField("exif-ifd0-Orientation")
|
foreach (var field in vipsImage.GetFields())
|
||||||
// but we'll keep the ImageSharp exif handling for now
|
{
|
||||||
var exifProfile = imageSharp.Metadata.ExifProfile;
|
var value = vipsImage.Get(field);
|
||||||
if (exifProfile?.Values.FirstOrDefault(e => e.Tag == ExifTag.Orientation)
|
exif.Add(field, value);
|
||||||
?.GetValue() is ushort o)
|
if (field == "orientation") orientation = (int)value;
|
||||||
orientation = o;
|
}
|
||||||
|
|
||||||
if (orientation is 6 or 8)
|
if (orientation is 6 or 8)
|
||||||
(width, height) = (height, width);
|
(width, height) = (height, width);
|
||||||
@@ -138,9 +131,6 @@ public class FileService(
|
|||||||
{
|
{
|
||||||
file.MimeType = "image/webp";
|
file.MimeType = "image/webp";
|
||||||
|
|
||||||
List<Task> tasks = [];
|
|
||||||
|
|
||||||
var ogFilePath = Path.Join(configuration.GetValue<string>("Tus:StorePath"), file.Id);
|
|
||||||
using var vipsImage = NetVips.Image.NewFromFile(ogFilePath);
|
using var vipsImage = NetVips.Image.NewFromFile(ogFilePath);
|
||||||
var imagePath = Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{file.Id}");
|
var imagePath = Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{file.Id}");
|
||||||
vipsImage.WriteToFile(imagePath + ".webp");
|
vipsImage.WriteToFile(imagePath + ".webp");
|
||||||
@@ -159,8 +149,6 @@ public class FileService(
|
|||||||
result.Add((imageCompressedPath + ".webp", ".compressed"));
|
result.Add((imageCompressedPath + ".webp", ".compressed"));
|
||||||
file.HasCompression = true;
|
file.HasCompression = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.WhenAll(tasks);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ public class MessageReadReceiptFlushHandler(IServiceProvider serviceProvider) :
|
|||||||
{
|
{
|
||||||
public async Task FlushAsync(IReadOnlyList<MessageReadReceipt> items)
|
public async Task FlushAsync(IReadOnlyList<MessageReadReceipt> items)
|
||||||
{
|
{
|
||||||
using var scope = serviceProvider.CreateScope();
|
var distinctItems = items.DistinctBy(x => new { x.MessageId, x.SenderId }).ToList();
|
||||||
var db = scope.ServiceProvider.GetRequiredService<AppDatabase>();
|
|
||||||
|
|
||||||
await db.BulkInsertAsync(items);
|
using var scope = serviceProvider.CreateScope();
|
||||||
|
var db = scope.ServiceProvider.GetRequiredService<AppDatabase>(); await db.BulkInsertAsync(distinctItems);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,9 @@
|
|||||||
"FromName": "Alphabot",
|
"FromName": "Alphabot",
|
||||||
"SubjectPrefix": "Solar Network"
|
"SubjectPrefix": "Solar Network"
|
||||||
},
|
},
|
||||||
|
"RealtimeChat": {
|
||||||
|
"Provider": "cloudflare"
|
||||||
|
},
|
||||||
"GeoIp": {
|
"GeoIp": {
|
||||||
"DatabasePath": "./Keys/GeoLite2-City.mmdb"
|
"DatabasePath": "./Keys/GeoLite2-City.mmdb"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationMiddleware_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe49de78932194d52a02b07486c6d023a24600_003F2f_003F7ab1cc57_003FAuthenticationMiddleware_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationMiddleware_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe49de78932194d52a02b07486c6d023a24600_003F2f_003F7ab1cc57_003FAuthenticationMiddleware_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationSchemeOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe49de78932194d52a02b07486c6d023a24600_003Ff0_003F595b6eda_003FAuthenticationSchemeOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationSchemeOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe49de78932194d52a02b07486c6d023a24600_003Ff0_003F595b6eda_003FAuthenticationSchemeOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthorizationAppBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F2ff26593f91746d7a53418a46dc419d1f200_003F4b_003F56550da2_003FAuthorizationAppBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthorizationAppBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F2ff26593f91746d7a53418a46dc419d1f200_003F4b_003F56550da2_003FAuthorizationAppBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABlurHashEncoder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fb87f853683828cb934127af9a42b22cf516412af1e61ae2ff4935ae82aff_003FBlurHashEncoder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABodyBuilder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fc5c8aba04a29d49c65d772c9ffcd93ac7eb38ccbb49a5f506518a0b9bdcaa75_003FBodyBuilder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABodyBuilder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fc5c8aba04a29d49c65d772c9ffcd93ac7eb38ccbb49a5f506518a0b9bdcaa75_003FBodyBuilder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABucketArgs_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fd515fb889657fcdcace3fed90735057b458ff9e0bb60bded7c8fe8b3a4673c_003FBucketArgs_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABucketArgs_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fd515fb889657fcdcace3fed90735057b458ff9e0bb60bded7c8fe8b3a4673c_003FBucketArgs_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AChapterData_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffef366b36a224d469ff150d30f9a866d23c00_003Fe6_003F64a6c0f7_003FChapterData_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AChapterData_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffef366b36a224d469ff150d30f9a866d23c00_003Fe6_003F64a6c0f7_003FChapterData_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
|||||||
Reference in New Issue
Block a user