mirror of
https://github.com/Stone-Red-Code/EchoHub.git
synced 2026-09-04 09:06:07 +02:00
feat: add attachment file size support for messages and update upload limits
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
- New `Audio` message type — uploaded audio files (`.mp3`, `.wav`, `.ogg`, `.flac`, `.aac`, `.m4a`, `.wma`) are automatically detected and categorized
|
- New `Audio` message type — uploaded audio files (`.mp3`, `.wav`, `.ogg`, `.flac`, `.aac`, `.m4a`, `.wma`) are automatically detected and categorized
|
||||||
- TUI client renders audio messages with `♪ [Audio: filename] (Enter to play)` indicator
|
- TUI client renders audio messages with `♪ [Audio: filename] (Enter to play)` indicator
|
||||||
- Press Enter on an audio message to open the **Audio Player dialog** with animated wave visualization, play/pause/stop controls, and volume slider
|
- Press Enter on an audio message to open the **Audio Player dialog** with animated wave visualization, play/pause/stop controls, and volume slider
|
||||||
- Audio file upload limit raised to **20 MB** (separate from the 10 MB general file limit)
|
- Per-type upload limits: **10 MB** images, **10 MB** audio, **100 MB** generic files (with Kestrel request size configured to match)
|
||||||
- `AudioPlaybackService` enhanced with pause/resume, volume control, and playback-finished events
|
- `AudioPlaybackService` enhanced with pause/resume, volume control, and playback-finished events
|
||||||
- Fixed: wrapped audio/file messages now remain clickable on all lines (attachment metadata propagated through word-wrap)
|
- Fixed: wrapped audio/file messages now remain clickable on all lines (attachment metadata propagated through word-wrap)
|
||||||
- IRC gateway formats audio messages as `♪ [Audio: filename] url`
|
- IRC gateway formats audio messages as `♪ [Audio: filename] url`
|
||||||
|
|||||||
@@ -1022,8 +1022,9 @@ public sealed class MainWindow : Runnable
|
|||||||
|
|
||||||
case MessageType.Audio:
|
case MessageType.Audio:
|
||||||
var audioName = message.AttachmentFileName ?? "unknown";
|
var audioName = message.AttachmentFileName ?? "unknown";
|
||||||
|
var audioSize = FormatFileSize(message.AttachmentFileSize);
|
||||||
var audioLine = BuildChatLineColored(time, senderName, senderColor,
|
var audioLine = BuildChatLineColored(time, senderName, senderColor,
|
||||||
$" \u266a [Audio: {audioName}] (Enter to play)", ChatColors.AudioAttr);
|
$" \u266a [Audio: {audioName}] [{audioSize}]", ChatColors.AudioAttr);
|
||||||
audioLine.AttachmentUrl = message.AttachmentUrl;
|
audioLine.AttachmentUrl = message.AttachmentUrl;
|
||||||
audioLine.AttachmentFileName = audioName;
|
audioLine.AttachmentFileName = audioName;
|
||||||
audioLine.Type = MessageType.Audio;
|
audioLine.Type = MessageType.Audio;
|
||||||
@@ -1032,8 +1033,9 @@ public sealed class MainWindow : Runnable
|
|||||||
|
|
||||||
case MessageType.File:
|
case MessageType.File:
|
||||||
var fileName = message.AttachmentFileName ?? "unknown";
|
var fileName = message.AttachmentFileName ?? "unknown";
|
||||||
|
var fileSize = FormatFileSize(message.AttachmentFileSize);
|
||||||
var fileLine = BuildChatLineColored(time, senderName, senderColor,
|
var fileLine = BuildChatLineColored(time, senderName, senderColor,
|
||||||
$" [File: {fileName}] (Enter to download)", ChatColors.FileAttr);
|
$" [File: {fileName}] [{fileSize}]", ChatColors.FileAttr);
|
||||||
fileLine.AttachmentUrl = message.AttachmentUrl;
|
fileLine.AttachmentUrl = message.AttachmentUrl;
|
||||||
fileLine.AttachmentFileName = fileName;
|
fileLine.AttachmentFileName = fileName;
|
||||||
fileLine.Type = MessageType.File;
|
fileLine.Type = MessageType.File;
|
||||||
@@ -1203,4 +1205,18 @@ public sealed class MainWindow : Runnable
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string FormatFileSize(long? bytes)
|
||||||
|
{
|
||||||
|
if (bytes is null or 0)
|
||||||
|
return "?";
|
||||||
|
|
||||||
|
return bytes.Value switch
|
||||||
|
{
|
||||||
|
< 1024 => $"{bytes.Value} B",
|
||||||
|
< 1024 * 1024 => $"{bytes.Value / 1024.0:F1} KB",
|
||||||
|
< 1024 * 1024 * 1024 => $"{bytes.Value / (1024.0 * 1024.0):F1} MB",
|
||||||
|
_ => $"{bytes.Value / (1024.0 * 1024.0 * 1024.0):F1} GB"
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ public static class HubConstants
|
|||||||
public const string DefaultChannel = "general";
|
public const string DefaultChannel = "general";
|
||||||
public const int DefaultHistoryCount = 100;
|
public const int DefaultHistoryCount = 100;
|
||||||
public const int MaxMessageLength = 2000;
|
public const int MaxMessageLength = 2000;
|
||||||
public const int MaxFileSizeBytes = 10 * 1024 * 1024; // 10 MB
|
public const int MaxImageSizeBytes = 10 * 1024 * 1024; // 10 MB
|
||||||
public const int MaxAudioFileSizeBytes = 20 * 1024 * 1024; // 20 MB
|
public const int MaxAudioFileSizeBytes = 10 * 1024 * 1024; // 10 MB
|
||||||
|
public const int MaxFileSizeBytes = 100 * 1024 * 1024; // 100 MB
|
||||||
public const int MaxAvatarSizeBytes = 2 * 1024 * 1024; // 2 MB
|
public const int MaxAvatarSizeBytes = 2 * 1024 * 1024; // 2 MB
|
||||||
public const int MaxMessageNewlines = 30;
|
public const int MaxMessageNewlines = 30;
|
||||||
public const int MaxConsecutiveNewlines = 1;
|
public const int MaxConsecutiveNewlines = 1;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public record MessageDto(
|
|||||||
string? AttachmentUrl,
|
string? AttachmentUrl,
|
||||||
string? AttachmentFileName,
|
string? AttachmentFileName,
|
||||||
DateTimeOffset SentAt,
|
DateTimeOffset SentAt,
|
||||||
|
long? AttachmentFileSize = null,
|
||||||
List<EmbedDto>? Embeds = null);
|
List<EmbedDto>? Embeds = null);
|
||||||
|
|
||||||
public record ChannelDto(
|
public record ChannelDto(
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public class Message
|
|||||||
public MessageType Type { get; set; } = MessageType.Text;
|
public MessageType Type { get; set; } = MessageType.Text;
|
||||||
public string? AttachmentUrl { get; set; }
|
public string? AttachmentUrl { get; set; }
|
||||||
public string? AttachmentFileName { get; set; }
|
public string? AttachmentFileName { get; set; }
|
||||||
|
public long? AttachmentFileSize { get; set; }
|
||||||
public string? EmbedJson { get; set; }
|
public string? EmbedJson { get; set; }
|
||||||
public DateTimeOffset SentAt { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset SentAt { get; set; } = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,8 @@ public class ChannelsController : ControllerBase
|
|||||||
|
|
||||||
[HttpPost("{channel}/upload")]
|
[HttpPost("{channel}/upload")]
|
||||||
[EnableRateLimiting("upload")]
|
[EnableRateLimiting("upload")]
|
||||||
|
[RequestSizeLimit(HubConstants.MaxFileSizeBytes)]
|
||||||
|
[RequestFormLimits(MultipartBodyLengthLimit = HubConstants.MaxFileSizeBytes)]
|
||||||
public async Task<IActionResult> Upload(string channel, [FromQuery] string? size = null)
|
public async Task<IActionResult> Upload(string channel, [FromQuery] string? size = null)
|
||||||
{
|
{
|
||||||
var userIdClaim = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
var userIdClaim = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
@@ -130,17 +132,17 @@ public class ChannelsController : ControllerBase
|
|||||||
var file = Request.Form.Files[0];
|
var file = Request.Form.Files[0];
|
||||||
|
|
||||||
// Detect file type early so we can apply the correct size limit
|
// Detect file type early so we can apply the correct size limit
|
||||||
var isAudioByExtension = FileValidationHelper.IsAudioFile(file.FileName);
|
using var stream = file.OpenReadStream();
|
||||||
var maxSize = isAudioByExtension ? HubConstants.MaxAudioFileSizeBytes : HubConstants.MaxFileSizeBytes;
|
var isImage = FileValidationHelper.IsValidImage(stream);
|
||||||
|
var isAudio = !isImage && FileValidationHelper.IsAudioFile(file.FileName);
|
||||||
|
|
||||||
|
var maxSize = isImage ? HubConstants.MaxImageSizeBytes
|
||||||
|
: isAudio ? HubConstants.MaxAudioFileSizeBytes
|
||||||
|
: HubConstants.MaxFileSizeBytes;
|
||||||
|
|
||||||
if (file.Length > maxSize)
|
if (file.Length > maxSize)
|
||||||
return BadRequest(new ErrorResponse($"File size exceeds maximum of {maxSize / (1024 * 1024)} MB."));
|
return BadRequest(new ErrorResponse($"File size exceeds maximum of {maxSize / (1024 * 1024)} MB."));
|
||||||
|
|
||||||
// Detect file type: image (magic bytes), audio (extension), or generic file
|
|
||||||
using var stream = file.OpenReadStream();
|
|
||||||
var isImage = FileValidationHelper.IsValidImage(stream);
|
|
||||||
var isAudio = !isImage && isAudioByExtension;
|
|
||||||
|
|
||||||
var (fileId, filePath) = await _fileStorage.SaveFileAsync(stream, file.FileName);
|
var (fileId, filePath) = await _fileStorage.SaveFileAsync(stream, file.FileName);
|
||||||
|
|
||||||
var messageType = isImage ? MessageType.Image
|
var messageType = isImage ? MessageType.Image
|
||||||
@@ -170,6 +172,7 @@ public class ChannelsController : ControllerBase
|
|||||||
Type = messageType,
|
Type = messageType,
|
||||||
AttachmentUrl = attachmentUrl,
|
AttachmentUrl = attachmentUrl,
|
||||||
AttachmentFileName = file.FileName,
|
AttachmentFileName = file.FileName,
|
||||||
|
AttachmentFileSize = file.Length,
|
||||||
SentAt = DateTimeOffset.UtcNow,
|
SentAt = DateTimeOffset.UtcNow,
|
||||||
ChannelId = channelDto.Id,
|
ChannelId = channelDto.Id,
|
||||||
SenderUserId = userId,
|
SenderUserId = userId,
|
||||||
@@ -189,7 +192,8 @@ public class ChannelsController : ControllerBase
|
|||||||
messageType,
|
messageType,
|
||||||
attachmentUrl,
|
attachmentUrl,
|
||||||
file.FileName,
|
file.FileName,
|
||||||
message.SentAt);
|
message.SentAt,
|
||||||
|
file.Length);
|
||||||
|
|
||||||
await _chatService.BroadcastMessageAsync(channelName, messageDto);
|
await _chatService.BroadcastMessageAsync(channelName, messageDto);
|
||||||
|
|
||||||
@@ -232,13 +236,13 @@ public class ChannelsController : ControllerBase
|
|||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
var contentLength = response.Content.Headers.ContentLength;
|
var contentLength = response.Content.Headers.ContentLength;
|
||||||
if (contentLength > HubConstants.MaxFileSizeBytes)
|
if (contentLength > HubConstants.MaxImageSizeBytes)
|
||||||
return BadRequest(new ErrorResponse($"File size exceeds maximum of {HubConstants.MaxFileSizeBytes / (1024 * 1024)} MB."));
|
return BadRequest(new ErrorResponse($"File size exceeds maximum of {HubConstants.MaxImageSizeBytes / (1024 * 1024)} MB."));
|
||||||
|
|
||||||
imageBytes = await response.Content.ReadAsByteArrayAsync();
|
imageBytes = await response.Content.ReadAsByteArrayAsync();
|
||||||
|
|
||||||
if (imageBytes.Length > HubConstants.MaxFileSizeBytes)
|
if (imageBytes.Length > HubConstants.MaxImageSizeBytes)
|
||||||
return BadRequest(new ErrorResponse($"File size exceeds maximum of {HubConstants.MaxFileSizeBytes / (1024 * 1024)} MB."));
|
return BadRequest(new ErrorResponse($"File size exceeds maximum of {HubConstants.MaxImageSizeBytes / (1024 * 1024)} MB."));
|
||||||
|
|
||||||
fileName = Path.GetFileName(uri.LocalPath);
|
fileName = Path.GetFileName(uri.LocalPath);
|
||||||
if (string.IsNullOrWhiteSpace(fileName) || !fileName.Contains('.'))
|
if (string.IsNullOrWhiteSpace(fileName) || !fileName.Contains('.'))
|
||||||
@@ -290,6 +294,7 @@ public class ChannelsController : ControllerBase
|
|||||||
Type = MessageType.Image,
|
Type = MessageType.Image,
|
||||||
AttachmentUrl = attachmentUrl,
|
AttachmentUrl = attachmentUrl,
|
||||||
AttachmentFileName = fileName,
|
AttachmentFileName = fileName,
|
||||||
|
AttachmentFileSize = imageBytes.Length,
|
||||||
SentAt = DateTimeOffset.UtcNow,
|
SentAt = DateTimeOffset.UtcNow,
|
||||||
ChannelId = channelDto.Id,
|
ChannelId = channelDto.Id,
|
||||||
SenderUserId = userId,
|
SenderUserId = userId,
|
||||||
@@ -309,7 +314,8 @@ public class ChannelsController : ControllerBase
|
|||||||
MessageType.Image,
|
MessageType.Image,
|
||||||
attachmentUrl,
|
attachmentUrl,
|
||||||
fileName,
|
fileName,
|
||||||
message.SentAt);
|
message.SentAt,
|
||||||
|
imageBytes.Length);
|
||||||
|
|
||||||
await _chatService.BroadcastMessageAsync(channelName, messageDto);
|
await _chatService.BroadcastMessageAsync(channelName, messageDto);
|
||||||
|
|
||||||
|
|||||||
+267
@@ -0,0 +1,267 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using EchoHub.Server.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace EchoHub.Server.Data.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(EchoHubDbContext))]
|
||||||
|
[Migration("20260221193444_AddAttachmentFileSize")]
|
||||||
|
partial class AddAttachmentFileSize
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "10.0.3");
|
||||||
|
|
||||||
|
modelBuilder.Entity("EchoHub.Core.Models.Channel", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("CreatedAt")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid>("CreatedByUserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsPublic")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Topic")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Channels");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EchoHub.Core.Models.ChannelMembership", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("ChannelId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("JoinedAt")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "ChannelId");
|
||||||
|
|
||||||
|
b.HasIndex("ChannelId");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("ChannelMemberships");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EchoHub.Core.Models.Message", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("AttachmentFileName")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long?>("AttachmentFileSize")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("AttachmentUrl")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("ChannelId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Content")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(16000)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("EmbedJson")
|
||||||
|
.HasMaxLength(32000)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("SenderUserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SenderUsername")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("SentAt")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ChannelId");
|
||||||
|
|
||||||
|
b.HasIndex("SentAt");
|
||||||
|
|
||||||
|
b.ToTable("Messages");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EchoHub.Core.Models.RefreshToken", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("CreatedAt")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("ExpiresAt")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long?>("RevokedAt")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("TokenHash")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TokenHash");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("RefreshTokens");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EchoHub.Core.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("AvatarAscii")
|
||||||
|
.HasMaxLength(10000)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Bio")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("CreatedAt")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsBanned")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("IsMuted")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("LastSeenAt")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long?>("MutedUntil")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("NicknameColor")
|
||||||
|
.HasMaxLength(7)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Role")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("StatusMessage")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Username")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EchoHub.Core.Models.ChannelMembership", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("EchoHub.Core.Models.Channel", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ChannelId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("EchoHub.Core.Models.User", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EchoHub.Core.Models.Message", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("EchoHub.Core.Models.Channel", "Channel")
|
||||||
|
.WithMany("Messages")
|
||||||
|
.HasForeignKey("ChannelId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Channel");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EchoHub.Core.Models.RefreshToken", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("EchoHub.Core.Models.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EchoHub.Core.Models.Channel", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Messages");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace EchoHub.Server.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddAttachmentFileSize : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<long>(
|
||||||
|
name: "AttachmentFileSize",
|
||||||
|
table: "Messages",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "AttachmentFileSize",
|
||||||
|
table: "Messages");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -79,6 +79,9 @@ namespace EchoHub.Server.Data.Migrations
|
|||||||
.HasMaxLength(255)
|
.HasMaxLength(255)
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long?>("AttachmentFileSize")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<string>("AttachmentUrl")
|
b.Property<string>("AttachmentUrl")
|
||||||
.HasMaxLength(500)
|
.HasMaxLength(500)
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ public class ChatService : IChatService
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
message.SentAt,
|
message.SentAt,
|
||||||
embeds);
|
Embeds: embeds);
|
||||||
|
|
||||||
await BroadcastToAllAsync(b => b.SendMessageToChannelAsync(channelName, messageDto));
|
await BroadcastToAllAsync(b => b.SendMessageToChannelAsync(channelName, messageDto));
|
||||||
|
|
||||||
@@ -416,6 +416,7 @@ public class ChatService : IChatService
|
|||||||
x.m.AttachmentUrl,
|
x.m.AttachmentUrl,
|
||||||
x.m.AttachmentFileName,
|
x.m.AttachmentFileName,
|
||||||
x.m.SentAt,
|
x.m.SentAt,
|
||||||
|
x.m.AttachmentFileSize,
|
||||||
embeds);
|
embeds);
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public class IrcMessageFormatterTests
|
|||||||
{
|
{
|
||||||
return new MessageDto(
|
return new MessageDto(
|
||||||
Guid.NewGuid(), content, sender, null, channel,
|
Guid.NewGuid(), content, sender, null, channel,
|
||||||
MessageType.Text, null, null, DateTimeOffset.UtcNow, embeds);
|
MessageType.Text, null, null, DateTimeOffset.UtcNow, Embeds: embeds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MessageDto CreateImageMessage(string asciiArt, string fileName = "image.png",
|
private static MessageDto CreateImageMessage(string asciiArt, string fileName = "image.png",
|
||||||
|
|||||||
Reference in New Issue
Block a user