Merge branch 'main' into feature-whiteboard-query-getall

# Conflicts:
#	dotnet/AipsWebApi/Controllers/WhiteboardController.cs
This commit is contained in:
2026-02-12 00:41:25 +01:00
25 changed files with 409 additions and 11 deletions

Binary file not shown.

View File

@@ -0,0 +1,8 @@
using AipsCore.Domain.Models.User.ValueObjects;
namespace AipsCore.Application.Abstract.UserContext;
public interface IUserContext
{
UserId GetCurrentUserId();
}

View File

@@ -0,0 +1,5 @@
using AipsCore.Application.Abstract.Command;
namespace AipsCore.Application.Models.Whiteboard.Command.BanUserFromWhiteboard;
public record BanUserFromWhiteboardCommand(string UserId, string WhiteboardId) : ICommand;

View File

@@ -0,0 +1,18 @@
using AipsCore.Domain.Common.Validation;
using AipsCore.Domain.Models.User.ValueObjects;
using AipsCore.Domain.Models.Whiteboard.ValueObjects;
namespace AipsCore.Application.Models.Whiteboard.Command.BanUserFromWhiteboard;
public static class BanUserFromWhiteboardCommandErrors
{
public static ValidationError WhiteboardMembershipNotFound(WhiteboardId whiteboardId, UserId userId)
=> new ValidationError(
Code: "whiteboard_membership_not_found",
Message: $"User with id '{userId.IdValue}' is not a member of whiteboard with id '{whiteboardId.IdValue}'");
public static ValidationError WhiteboardNotFound(WhiteboardId whiteboardId)
=> new ValidationError(
Code: "whiteboard_not_found",
Message: $"Whiteboard with id '{whiteboardId.IdValue}' not found.");
}

View File

@@ -0,0 +1,55 @@
using AipsCore.Application.Abstract.Command;
using AipsCore.Application.Abstract.UserContext;
using AipsCore.Domain.Abstract;
using AipsCore.Domain.Common.Validation;
using AipsCore.Domain.Models.User.ValueObjects;
using AipsCore.Domain.Models.Whiteboard.External;
using AipsCore.Domain.Models.Whiteboard.ValueObjects;
using AipsCore.Domain.Models.WhiteboardMembership.External;
namespace AipsCore.Application.Models.Whiteboard.Command.BanUserFromWhiteboard;
public class BanUserFromWhiteboardCommandHandler : ICommandHandler<BanUserFromWhiteboardCommand>
{
private readonly IWhiteboardRepository _whiteboardRepository;
private readonly IWhiteboardMembershipRepository _whiteboardMembershipRepository;
private readonly IUserContext _userContext;
private readonly IUnitOfWork _unitOfWork;
public BanUserFromWhiteboardCommandHandler(
IWhiteboardRepository whiteboardRepository,
IWhiteboardMembershipRepository whiteboardMembershipRepository,
IUserContext userContext,
IUnitOfWork unitOfWork)
{
_whiteboardMembershipRepository = whiteboardMembershipRepository;
_userContext = userContext;
_unitOfWork = unitOfWork;
_whiteboardRepository = whiteboardRepository;
}
public async Task Handle(BanUserFromWhiteboardCommand command, CancellationToken cancellationToken = default)
{
var whiteboardId = new WhiteboardId(command.WhiteboardId);
var userId = new UserId(command.UserId);
var whiteboard = await _whiteboardRepository.GetByIdAsync(whiteboardId, cancellationToken);
if (whiteboard is null)
{
throw new ValidationException(BanUserFromWhiteboardCommandErrors.WhiteboardNotFound(whiteboardId));
}
var membership = await _whiteboardMembershipRepository.GetByWhiteboardAndUserAsync(whiteboardId, userId, cancellationToken);
if (membership is null)
{
throw new ValidationException(BanUserFromWhiteboardCommandErrors.WhiteboardMembershipNotFound(whiteboardId, userId));
}
whiteboard.BanUser(_userContext.GetCurrentUserId(), membership);
await _whiteboardMembershipRepository.SaveAsync(membership, cancellationToken);
await _unitOfWork.SaveChangesAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,5 @@
using AipsCore.Application.Abstract.Command;
namespace AipsCore.Application.Models.Whiteboard.Command.KickUserFromWhiteboard;
public record KickUserFromWhiteboardCommand(string UserId, string WhiteboardId) : ICommand;

View File

@@ -0,0 +1,18 @@
using AipsCore.Domain.Common.Validation;
using AipsCore.Domain.Models.User.ValueObjects;
using AipsCore.Domain.Models.Whiteboard.ValueObjects;
namespace AipsCore.Application.Models.Whiteboard.Command.KickUserFromWhiteboard;
public static class KickUserFromWhiteboardCommandErrors
{
public static ValidationError WhiteboardMembershipNotFound(WhiteboardId whiteboardId, UserId userId)
=> new ValidationError(
Code: "whiteboard_membership_not_found",
Message: $"User with id '{userId}' is not a member of whiteboard with id '{whiteboardId}'");
public static ValidationError WhiteboardNotFound(WhiteboardId whiteboardId)
=> new ValidationError(
Code: "whiteboard_not_found",
Message: $"Whiteboard with id '{whiteboardId}' not found.");
}

View File

@@ -0,0 +1,55 @@
using AipsCore.Application.Abstract.Command;
using AipsCore.Application.Abstract.UserContext;
using AipsCore.Domain.Abstract;
using AipsCore.Domain.Common.Validation;
using AipsCore.Domain.Models.User.ValueObjects;
using AipsCore.Domain.Models.Whiteboard.External;
using AipsCore.Domain.Models.Whiteboard.ValueObjects;
using AipsCore.Domain.Models.WhiteboardMembership.External;
namespace AipsCore.Application.Models.Whiteboard.Command.KickUserFromWhiteboard;
public class KickUserFromWhiteboardCommandHandler : ICommandHandler<KickUserFromWhiteboardCommand>
{
private readonly IWhiteboardRepository _whiteboardRepository;
private readonly IWhiteboardMembershipRepository _whiteboardMembershipRepository;
private readonly IUserContext _userContext;
private readonly IUnitOfWork _unitOfWork;
public KickUserFromWhiteboardCommandHandler(
IWhiteboardRepository whiteboardRepository,
IWhiteboardMembershipRepository whiteboardMembershipRepository,
IUserContext userContext,
IUnitOfWork unitOfWork)
{
_whiteboardMembershipRepository = whiteboardMembershipRepository;
_userContext = userContext;
_unitOfWork = unitOfWork;
_whiteboardRepository = whiteboardRepository;
}
public async Task Handle(KickUserFromWhiteboardCommand command, CancellationToken cancellationToken = default)
{
var whiteboardId = new WhiteboardId(command.WhiteboardId);
var userId = new UserId(command.UserId);
var whiteboard = await _whiteboardRepository.GetByIdAsync(whiteboardId, cancellationToken);
if (whiteboard is null)
{
throw new ValidationException(KickUserFromWhiteboardCommandErrors.WhiteboardNotFound(whiteboardId));
}
var membership = await _whiteboardMembershipRepository.GetByWhiteboardAndUserAsync(whiteboardId, userId, cancellationToken);
if (membership is null)
{
throw new ValidationException(KickUserFromWhiteboardCommandErrors.WhiteboardMembershipNotFound(whiteboardId, userId));
}
whiteboard.KickUser(_userContext.GetCurrentUserId(), membership);
await _whiteboardMembershipRepository.SaveAsync(membership, cancellationToken);
await _unitOfWork.SaveChangesAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,5 @@
using AipsCore.Application.Abstract.Command;
namespace AipsCore.Application.Models.Whiteboard.Command.UnbanUserFromWhiteboard;
public record UnbanUserFromWhiteboardCommand(string UserId, string WhiteboardId) : ICommand;

View File

@@ -0,0 +1,18 @@
using AipsCore.Domain.Common.Validation;
using AipsCore.Domain.Models.User.ValueObjects;
using AipsCore.Domain.Models.Whiteboard.ValueObjects;
namespace AipsCore.Application.Models.Whiteboard.Command.UnbanUserFromWhiteboard;
public static class UnbanUserFromWhiteboardCommandErrors
{
public static ValidationError WhiteboardMembershipNotFound(WhiteboardId whiteboardId, UserId userId)
=> new ValidationError(
Code: "whiteboard_membership_not_found",
Message: $"User with id '{userId}' is not a member of whiteboard with id '{whiteboardId}'");
public static ValidationError WhiteboardNotFound(WhiteboardId whiteboardId)
=> new ValidationError(
Code: "whiteboard_not_found",
Message: $"Whiteboard with id '{whiteboardId}' not found.");
}

View File

@@ -0,0 +1,55 @@
using AipsCore.Application.Abstract.Command;
using AipsCore.Application.Abstract.UserContext;
using AipsCore.Domain.Abstract;
using AipsCore.Domain.Common.Validation;
using AipsCore.Domain.Models.User.ValueObjects;
using AipsCore.Domain.Models.Whiteboard.External;
using AipsCore.Domain.Models.Whiteboard.ValueObjects;
using AipsCore.Domain.Models.WhiteboardMembership.External;
namespace AipsCore.Application.Models.Whiteboard.Command.UnbanUserFromWhiteboard;
public class UnbanUserFromWhiteboardCommandHandler : ICommandHandler<UnbanUserFromWhiteboardCommand>
{
private readonly IWhiteboardRepository _whiteboardRepository;
private readonly IWhiteboardMembershipRepository _whiteboardMembershipRepository;
private readonly IUserContext _userContext;
private readonly IUnitOfWork _unitOfWork;
public UnbanUserFromWhiteboardCommandHandler(
IWhiteboardRepository whiteboardRepository,
IWhiteboardMembershipRepository whiteboardMembershipRepository,
IUserContext userContext,
IUnitOfWork unitOfWork)
{
_whiteboardRepository = whiteboardRepository;
_whiteboardMembershipRepository = whiteboardMembershipRepository;
_userContext = userContext;
_unitOfWork = unitOfWork;
}
public async Task Handle(UnbanUserFromWhiteboardCommand command, CancellationToken cancellationToken = default)
{
var whiteboardId = new WhiteboardId(command.WhiteboardId);
var userId = new UserId(command.UserId);
var whiteboard = await _whiteboardRepository.GetByIdAsync(whiteboardId, cancellationToken);
if (whiteboard is null)
{
throw new ValidationException(UnbanUserFromWhiteboardCommandErrors.WhiteboardNotFound(whiteboardId));
}
var membership = await _whiteboardMembershipRepository.GetByWhiteboardAndUserAsync(whiteboardId, userId, cancellationToken);
if (membership is null)
{
throw new ValidationException(UnbanUserFromWhiteboardCommandErrors.WhiteboardMembershipNotFound(whiteboardId, userId));
}
whiteboard.UnbanUser(_userContext.GetCurrentUserId(), membership);
await _whiteboardMembershipRepository.SaveAsync(membership, cancellationToken);
await _unitOfWork.SaveChangesAsync(cancellationToken);
}
}

View File

@@ -22,9 +22,4 @@ public record DomainId : AbstractValueObject
new MinLengthRule(IdValue, 5) new MinLengthRule(IdValue, 5)
]; ];
} }
public override string ToString()
{
return IdValue;
}
} }

View File

@@ -15,4 +15,27 @@ public class WhiteboardErrors : AbstractErrors<Whiteboard, WhiteboardId>
return CreateValidationError(code, message); return CreateValidationError(code, message);
} }
public static ValidationError OnlyOwnerCanBanOtherUsers(UserId currentUserId)
{
string code = "only_owner_can_ban_other_users";
string message = $"Only owner of whiteboard can ban other users. Current user id: '{currentUserId}' is not the owner.";
return CreateValidationError(code, message);
}
public static ValidationError OnlyOwnerCanUnbanOtherUsers(UserId currentUserId)
{
string code = "only_owner_can_unban_other_users";
string message = $"Only owner of whiteboard can unban other users. Current user id: '{currentUserId}' is not the owner.";
return CreateValidationError(code, message);
}
public static ValidationError OnlyOwnerCanKickOtherUsers(UserId currentUserId)
{
string code = "only_owner_can_unban_other_users";
string message = $"Only owner of whiteboard can kick other users. Current user id: '{currentUserId}' is not the owner.";
return CreateValidationError(code, message);
}
} }

View File

@@ -0,0 +1,18 @@
using AipsCore.Domain.Common.Validation;
using AipsCore.Domain.Models.User.ValueObjects;
using AipsCore.Domain.Models.Whiteboard.Validation;
namespace AipsCore.Domain.Models.Whiteboard;
public partial class Whiteboard
{
public void BanUser(UserId currentUserId, WhiteboardMembership.WhiteboardMembership whiteboardMembership)
{
if (WhiteboardOwnerId != currentUserId)
{
throw new ValidationException(WhiteboardErrors.OnlyOwnerCanBanOtherUsers(currentUserId));
}
whiteboardMembership.Ban();
}
}

View File

@@ -0,0 +1,18 @@
using AipsCore.Domain.Common.Validation;
using AipsCore.Domain.Models.User.ValueObjects;
using AipsCore.Domain.Models.Whiteboard.Validation;
namespace AipsCore.Domain.Models.Whiteboard;
public partial class Whiteboard
{
public void KickUser(UserId currentUserId, WhiteboardMembership.WhiteboardMembership whiteboardMembership)
{
if (WhiteboardOwnerId != currentUserId)
{
throw new ValidationException(WhiteboardErrors.OnlyOwnerCanKickOtherUsers(currentUserId));
}
whiteboardMembership.Kick();
}
}

View File

@@ -0,0 +1,18 @@
using AipsCore.Domain.Common.Validation;
using AipsCore.Domain.Models.User.ValueObjects;
using AipsCore.Domain.Models.Whiteboard.Validation;
namespace AipsCore.Domain.Models.Whiteboard;
public partial class Whiteboard
{
public void UnbanUser(UserId currentUserId, WhiteboardMembership.WhiteboardMembership whiteboardMembership)
{
if (WhiteboardOwnerId != currentUserId)
{
throw new ValidationException(WhiteboardErrors.OnlyOwnerCanUnbanOtherUsers(currentUserId));
}
whiteboardMembership.Unban();
}
}

View File

@@ -0,0 +1,11 @@
using AipsCore.Domain.Models.WhiteboardMembership.ValueObjects;
namespace AipsCore.Domain.Models.WhiteboardMembership;
public partial class WhiteboardMembership
{
public void Ban()
{
IsBanned = new WhiteboardMembershipIsBanned(true);
}
}

View File

@@ -0,0 +1,11 @@
using AipsCore.Domain.Models.WhiteboardMembership.ValueObjects;
namespace AipsCore.Domain.Models.WhiteboardMembership;
public partial class WhiteboardMembership
{
public void Kick()
{
CanJoin = new WhiteboardMembershipCanJoin(false);
}
}

View File

@@ -0,0 +1,11 @@
using AipsCore.Domain.Models.WhiteboardMembership.ValueObjects;
namespace AipsCore.Domain.Models.WhiteboardMembership;
public partial class WhiteboardMembership
{
public void Unban()
{
IsBanned = new WhiteboardMembershipIsBanned(false);
}
}

View File

@@ -5,7 +5,7 @@ using AipsCore.Domain.Models.WhiteboardMembership.ValueObjects;
namespace AipsCore.Domain.Models.WhiteboardMembership; namespace AipsCore.Domain.Models.WhiteboardMembership;
public class WhiteboardMembership : DomainModel<WhiteboardMembershipId> public partial class WhiteboardMembership : DomainModel<WhiteboardMembershipId>
{ {
public WhiteboardId WhiteboardId { get; private set; } public WhiteboardId WhiteboardId { get; private set; }
public UserId UserId { get; private set; } public UserId UserId { get; private set; }

View File

@@ -1,8 +1,5 @@
using AipsCore.Application.Abstract; using AipsCore.Application.Abstract;
using AipsCore.Application.Abstract.Command;
using AipsCore.Application.Common.Dispatcher; using AipsCore.Application.Common.Dispatcher;
using AipsCore.Application.Models.User.Command.CreateUser;
using AipsCore.Domain.Models.User.ValueObjects;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -17,6 +14,8 @@ public static class AipsRegistrationExtensions
services.AddPersistence(configuration); services.AddPersistence(configuration);
services.AddUserContext();
return services; return services;
} }
} }

View File

@@ -0,0 +1,14 @@
using AipsCore.Application.Abstract.UserContext;
using Microsoft.Extensions.DependencyInjection;
namespace AipsCore.Infrastructure.DI;
public static class UserContextRegistrationExtension
{
public static IServiceCollection AddUserContext(this IServiceCollection services)
{
services.AddTransient<IUserContext, UserContext>();
return services;
}
}

View File

@@ -57,8 +57,8 @@ public class WhiteboardMembershipRepository
{ {
var whiteboardMembership = await Context.WhiteboardMemberships var whiteboardMembership = await Context.WhiteboardMemberships
.FirstOrDefaultAsync((entity) => .FirstOrDefaultAsync((entity) =>
entity.WhiteboardId.ToString() == whiteboardId.ToString() && entity.WhiteboardId.ToString() == whiteboardId.IdValue &&
entity.UserId.ToString() == userId.ToString(), entity.UserId.ToString() == userId.IdValue,
cancellationToken); cancellationToken);
if (whiteboardMembership is null) return null; if (whiteboardMembership is null) return null;

View File

@@ -0,0 +1,14 @@
using AipsCore.Application.Abstract.UserContext;
using AipsCore.Domain.Models.User.ValueObjects;
namespace AipsCore.Infrastructure;
public class UserContext : IUserContext
{
public UserId GetCurrentUserId()
{
return new UserId(new Guid("52a1810c-802f-48b0-a74c-7b517807e392").ToString());
}
}
//Ovo je samo trenutno resenje

View File

@@ -1,6 +1,9 @@
using AipsCore.Application.Abstract; using AipsCore.Application.Abstract;
using AipsCore.Application.Models.Whiteboard.Command.AddUserToWhiteboard; using AipsCore.Application.Models.Whiteboard.Command.AddUserToWhiteboard;
using AipsCore.Application.Models.Whiteboard.Command.BanUserFromWhiteboard;
using AipsCore.Application.Models.Whiteboard.Command.CreateWhiteboard; using AipsCore.Application.Models.Whiteboard.Command.CreateWhiteboard;
using AipsCore.Application.Models.Whiteboard.Command.KickUserFromWhiteboard;
using AipsCore.Application.Models.Whiteboard.Command.UnbanUserFromWhiteboard;
using AipsCore.Application.Models.Whiteboard.Query.GetRecentWhiteboards; using AipsCore.Application.Models.Whiteboard.Query.GetRecentWhiteboards;
using AipsCore.Domain.Models.Whiteboard; using AipsCore.Domain.Models.Whiteboard;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@@ -40,4 +43,25 @@ public class WhiteboardController : ControllerBase
return Ok(result); return Ok(result);
} }
[HttpPut("banUser")]
public async Task<ActionResult> BanUserFromWhiteboard(BanUserFromWhiteboardCommand command, IDispatcher dispatcher, CancellationToken cancellationToken)
{
await dispatcher.Execute(command, cancellationToken);
return Ok();
}
[HttpPut("unbanUser")]
public async Task<ActionResult> UnbanUserFromWhiteboard(UnbanUserFromWhiteboardCommand command, IDispatcher dispatcher, CancellationToken cancellationToken)
{
await dispatcher.Execute(command, cancellationToken);
return Ok();
}
[HttpPut("kickUser")]
public async Task<ActionResult> KickUserFromWhiteboard(KickUserFromWhiteboardCommand command, IDispatcher dispatcher, CancellationToken cancellationToken)
{
await dispatcher.Execute(command, cancellationToken);
return Ok();
}
} }