Зависимости на контракты
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
namespace Knot.Contracts.Admin.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Базовый класс для агрегатов - сущностей с бизнес-логикой и доменными событиями.
|
||||
/// </summary>
|
||||
public abstract class AggregateRoot<TId> : Entity<TId> where TId : notnull
|
||||
{
|
||||
private readonly List<IDomainEvent> _domainEvents = new();
|
||||
public IReadOnlyList<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();
|
||||
|
||||
protected AggregateRoot(TId id) : base(id) { }
|
||||
|
||||
protected void RaiseDomainEvent(IDomainEvent domainEvent)
|
||||
{
|
||||
_domainEvents.Add(domainEvent);
|
||||
}
|
||||
|
||||
public void ClearDomainEvents()
|
||||
{
|
||||
_domainEvents.Clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
namespace Knot.Contracts.Admin.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Базовый класс для сущностей с уникальным идентификатором.
|
||||
/// </summary>
|
||||
public abstract class Entity<TId> where TId : notnull
|
||||
{
|
||||
public TId Id { get; protected set; }
|
||||
|
||||
protected Entity(TId id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is not Entity<TId> other)
|
||||
return false;
|
||||
|
||||
if (ReferenceEquals(this, other))
|
||||
return true;
|
||||
|
||||
if (GetType() != other.GetType())
|
||||
return false;
|
||||
|
||||
return Id.Equals(other.Id);
|
||||
}
|
||||
|
||||
public override int GetHashCode() => Id.GetHashCode();
|
||||
|
||||
public static bool operator ==(Entity<TId>? a, Entity<TId>? b)
|
||||
{
|
||||
if (a is null && b is null)
|
||||
return true;
|
||||
|
||||
if (a is null || b is null)
|
||||
return false;
|
||||
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(Entity<TId>? a, Entity<TId>? b) => !(a == b);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using MediatR;
|
||||
|
||||
namespace Knot.Contracts.Admin.Abstractions;
|
||||
|
||||
public interface ICommand : IRequest<Result> { }
|
||||
|
||||
public interface ICommand<TResponse> : IRequest<Result<TResponse>> { }
|
||||
|
||||
public interface ICommandHandler<in TCommand> : IRequestHandler<TCommand, Result>
|
||||
where TCommand : ICommand
|
||||
{ }
|
||||
|
||||
public interface ICommandHandler<in TCommand, TResponse> : IRequestHandler<TCommand, Result<TResponse>>
|
||||
where TCommand : ICommand<TResponse>
|
||||
{ }
|
||||
@@ -0,0 +1,8 @@
|
||||
using MediatR;
|
||||
|
||||
namespace Knot.Contracts.Admin.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Интерфейс для доменных событий.
|
||||
/// </summary>
|
||||
public interface IDomainEvent : INotification { }
|
||||
@@ -0,0 +1,13 @@
|
||||
using MediatR;
|
||||
|
||||
namespace Knot.Contracts.Admin.Abstractions;
|
||||
|
||||
public interface IQuery<TResponse> : IRequest<Result<TResponse>>
|
||||
{
|
||||
}
|
||||
|
||||
public interface IQueryHandler<TQuery, TResponse>
|
||||
: IRequestHandler<TQuery, Result<TResponse>>
|
||||
where TQuery : IQuery<TResponse>
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Knot.Contracts.Admin.Abstractions;
|
||||
|
||||
public record MessageResponse(string Message);
|
||||
@@ -0,0 +1,63 @@
|
||||
namespace Knot.Contracts.Admin.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Представляет ошибку в доменной логике.
|
||||
/// </summary>
|
||||
public sealed record Error(string Code, string Description)
|
||||
{
|
||||
public static readonly Error None = new(string.Empty, string.Empty);
|
||||
public static Error NotFound(string code, string description) => new(code, description);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Общая обертка для результата операции. Позволяет избегать использования исключений для управления потоком.
|
||||
/// </summary>
|
||||
public class Result
|
||||
{
|
||||
public bool IsSuccess { get; }
|
||||
public bool IsFailure => !IsSuccess;
|
||||
public Error Error { get; }
|
||||
|
||||
protected Result(bool isSuccess, Error error)
|
||||
{
|
||||
if (isSuccess && error != Error.None)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
if (!isSuccess && error == Error.None)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
IsSuccess = isSuccess;
|
||||
Error = error;
|
||||
}
|
||||
|
||||
public static Result Success() => new(true, Error.None);
|
||||
public static Result Failure(Error error) => new(false, error);
|
||||
|
||||
public static Result<TValue> Success<TValue>(TValue value) => Result<TValue>.Success(value);
|
||||
public static Result<TValue> Failure<TValue>(Error error) => Result<TValue>.Failure(error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Результ операции, содержащий значение.
|
||||
/// </summary>
|
||||
public class Result<TValue> : Result
|
||||
{
|
||||
private readonly TValue? _value;
|
||||
|
||||
protected internal Result(TValue? value, bool isSuccess, Error error)
|
||||
: base(isSuccess, error)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public TValue Value => IsSuccess
|
||||
? _value!
|
||||
: throw new InvalidOperationException("Нельзя получить значение ошибочного результата.");
|
||||
|
||||
public static Result<TValue> Success(TValue value) => new(value, true, Error.None);
|
||||
public new static Result<TValue> Failure(Error error) => new(default, false, error);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Knot.Contracts.Admin.Abstractions;
|
||||
|
||||
public record SuccessResponse(bool Success);
|
||||
@@ -6,10 +6,6 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MediatR" Version="12.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.4" />
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Contracts.Auth.Domain;
|
||||
|
||||
namespace Knot.Contracts.Auth.Infrastructure.Persistence;
|
||||
|
||||
public interface IAuthDbContext
|
||||
{
|
||||
IQueryable<UserContract> Users { get; }
|
||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -1,20 +1,18 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Contracts.Conversations.Application.Abstractions;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
|
||||
namespace Knot.Contracts.Conversations.Infrastructure.Persistence;
|
||||
|
||||
public interface IChatsDbContext : IChatsUnitOfWork
|
||||
public interface IChatsDbContext
|
||||
{
|
||||
IQueryable<Chat> Chats { get; }
|
||||
}
|
||||
|
||||
public class Chat : AggregateRoot<Guid>
|
||||
/// <summary>
|
||||
/// Упрощенная проекция чата для запросов (не доменная сущность).
|
||||
/// </summary>
|
||||
public class Chat
|
||||
{
|
||||
public Chat() : base(Guid.NewGuid()) { }
|
||||
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public string? Avatar { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Contracts.Klipy.Application.Abstractions;
|
||||
|
||||
public interface IKlipyClient
|
||||
{
|
||||
Task<bool> TestConnectionAsync(string apiKey, string appName, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Contracts.Messaging.Application.Abstractions;
|
||||
|
||||
public interface IMessageQueryService
|
||||
{
|
||||
Task<List<MessageInfo>> GetAllMessagesAsync(CancellationToken cancellationToken);
|
||||
Task<List<MessageInfo>> GetOrphanedMessagesAsync(HashSet<Guid> activeChatIds, CancellationToken cancellationToken);
|
||||
Task DeleteMessagesAsync(List<Guid> messageIds, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public record MessageInfo(Guid Id, Guid ChatId, bool IsDeleted, string? MediaUrl, List<MediaInfo>? Media);
|
||||
|
||||
public record MediaInfo(string Url);
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Contracts.Messaging.Application.Abstractions;
|
||||
|
||||
public interface IUserStatsService
|
||||
{
|
||||
Task<Dictionary<Guid, UserStats>> GetStatsForUsersAsync(List<Guid> userIds, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public record UserStats(int MessageCount, long StorageSize);
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Contracts.Settings.Abstractions;
|
||||
|
||||
public interface IStatisticsService
|
||||
{
|
||||
Task<DashboardStatsDto> GetDashboardStatsAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public class DashboardStatsDto
|
||||
{
|
||||
public long StorageUsedBytes { get; set; }
|
||||
public long StorageLimitBytes { get; set; }
|
||||
public long OnlineUsers { get; set; }
|
||||
public long OfflineUsers { get; set; }
|
||||
public long TotalUsers { get; set; }
|
||||
public List<ActivityStatDto> ActivityTimeline { get; set; } = new();
|
||||
public List<TopUserDto> TopUsersByMessages { get; set; } = new();
|
||||
public List<TopUserDto> TopUsersByStorage { get; set; } = new();
|
||||
}
|
||||
|
||||
public class ActivityStatDto
|
||||
{
|
||||
public DateTime Date { get; set; }
|
||||
public long Messages { get; set; }
|
||||
public long FilesSize { get; set; }
|
||||
}
|
||||
|
||||
public class TopUserDto
|
||||
{
|
||||
public Guid UserId { get; set; }
|
||||
public string Username { get; set; } = string.Empty;
|
||||
public long Value { get; set; } // messages count or bytes
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Contracts.Storage.Abstractions;
|
||||
|
||||
public interface IFileStorageService
|
||||
{
|
||||
// Загружает поток файла и возвращает его уникальный идентификатор (SHA256 хеш или GUID).
|
||||
Task<string> UploadFileAsync(Stream stream, string fileName, string contentType);
|
||||
|
||||
// Скачивает файл и возвращает его расшифрованный поток и тип содержимого.
|
||||
Task<(Stream Stream, string ContentType, string FileName)> DownloadFileAsync(string fileId);
|
||||
|
||||
// Удаляет файл из хранилища.
|
||||
Task DeleteFileAsync(string fileId);
|
||||
|
||||
// Получает список всех файлов в хранилище с их размерами.
|
||||
Task<IEnumerable<(string FileId, long Size)>> ListFilesAsync();
|
||||
|
||||
// Получает размер конкретного файла
|
||||
Task<long> GetFileSizeAsync(string fileId, CancellationToken ct = default);
|
||||
|
||||
// Получает размеры списка файлов
|
||||
Task<Dictionary<string, long>> GetFileSizesAsync(IEnumerable<string> fileIds, CancellationToken ct = default);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Contracts.Stories.Infrastructure.Persistence;
|
||||
|
||||
public interface IStoryCollection
|
||||
{
|
||||
Task<List<StoryContract>> GetAllAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public class StoryContract
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public string? MediaUrl { get; set; }
|
||||
public string? MediaType { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? ExpiresAt { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user