Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class CreateArticleCommandHandlerTests
{
private readonly Mock<IApplicationDbContext> _mockDbContext = new();
private readonly Mock<ISlugHelper> _mockSlugHelper = new();
private readonly Mock<ICurrentUserService> _mockCurrentUserService = new();
private readonly Mock<IUserContext> _mockUserContext = new();
private readonly Mock<IValidator<CreateArticleCommand>> _mockValidator = new();
private readonly CreateArticleCommandHandler _handler;

Expand All @@ -22,7 +22,7 @@ public CreateArticleCommandHandlerTests()
_handler = new CreateArticleCommandHandler(
_mockDbContext.Object,
_mockSlugHelper.Object,
_mockCurrentUserService.Object,
_mockUserContext.Object,
_mockValidator.Object
);
}
Expand Down Expand Up @@ -134,6 +134,7 @@ public async void Handle_Should_CallSaveChangesAsync_WhenGeneratedIdIsUnique()
_mockDbContext.Setup(x => x.Articles).Returns(mockArticlesDbSet.Object);
_mockDbContext.Setup(x => x.Categories).Returns(mockCategoriesDbSet.Object);
_mockDbContext.Setup(x => x.Revisions).Returns(mockRevisionsDbSet.Object);
_mockUserContext.Setup(x => x.UserId).Returns(Guid.Empty);
_mockSlugHelper.Setup(x => x.GenerateSlug("something extra cool")).Returns("something-extra-cool");
_mockValidator.Setup(v => v.Validate(It.IsAny<CreateArticleCommand>()))
.Returns(new ValidationResult());
Expand Down Expand Up @@ -163,6 +164,7 @@ public async void Handle_Should_ReturnSuccessResultWithArticleId_WhenGeneratedId
_mockDbContext.Setup(x => x.Articles).Returns(mockArticlesDbSet.Object);
_mockDbContext.Setup(x => x.Categories).Returns(mockCategoriesDbSet.Object);
_mockDbContext.Setup(x => x.Revisions).Returns(mockRevisionsDbSet.Object);
_mockUserContext.Setup(x => x.UserId).Returns(Guid.Empty);
_mockSlugHelper.Setup(x => x.GenerateSlug("something extra cool")).Returns("something-extra-cool");
_mockValidator.Setup(v => v.Validate(It.IsAny<CreateArticleCommand>()))
.Returns(new ValidationResult());
Expand Down Expand Up @@ -205,8 +207,8 @@ public async void Handle_Should_CreateRevisionWithContent_WhenGeneratedIdIsUniqu
_mockDbContext.Setup(x => x.Articles).Returns(mockArticlesDbSet.Object);
_mockDbContext.Setup(x => x.Categories).Returns(mockCategoriesDbSet.Object);
_mockDbContext.Setup(x => x.Revisions).Returns(mockRevisionsDbSet.Object);
_mockUserContext.Setup(x => x.UserId).Returns(Guid.Empty);
_mockSlugHelper.Setup(x => x.GenerateSlug("something extra cool")).Returns("something-extra-cool");
_mockCurrentUserService.Setup(x => x.UserId).Returns("test-user-id");
_mockValidator.Setup(v => v.Validate(It.IsAny<CreateArticleCommand>()))
.Returns(new ValidationResult());
var expectedCategoryIds = new List<string> {"category1", "category2"};
Expand All @@ -222,7 +224,7 @@ public async void Handle_Should_CreateRevisionWithContent_WhenGeneratedIdIsUniqu
e.ArticleId == result.Value.Id
&& e.Content == command.Content
&& e.AuthorsNote == command.AuthorsNote
&& e.AuthorId == _mockCurrentUserService.Object.UserId
&& e.AuthorId == _mockUserContext.Object.UserId
&& e.Categories.Select(i => i.Id).OrderBy(i => i).SequenceEqual(expectedCategoryIds)
),
It.IsAny<CancellationToken>()
Expand Down
10 changes: 0 additions & 10 deletions Application/Authorization/Abstractions/ICurrentUserService.cs

This file was deleted.

8 changes: 0 additions & 8 deletions Application/Authorization/Abstractions/IIdentityService.cs

This file was deleted.

12 changes: 12 additions & 0 deletions Application/Authorization/Abstractions/IPermissionService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Domain.Entities;

namespace Application.Authorization.Abstractions;

public interface IPermissionService
{
Task<bool> HasPermissionAsync(Guid userId, Guid permissionId);
Task<bool> HasPermissionAsync(Guid userId, Permission permission);
Task<bool> HasAnyPermissionsAsync(Guid userId, IEnumerable<Guid> permissionIds);
Task<bool> HasAnyPermissionsAsync(Guid userId, IEnumerable<Permission> permissions);
Task<IEnumerable<Permission>> GetPermissionsAsync(Guid userId);
}
9 changes: 9 additions & 0 deletions Application/Authorization/Abstractions/IUserContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Domain.Entities;

namespace Application.Authorization.Abstractions;

public interface IUserContext
{
Guid? UserId { get; }
Task<User?> GetCurrentUserAsync();
}
46 changes: 46 additions & 0 deletions Application/Authorization/PermissionService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Application.Authorization.Abstractions;
using Application.Data;
using Domain.Entities;
using Microsoft.EntityFrameworkCore;

namespace Application.Authorization;

public class PermissionService : IPermissionService
{
private readonly IApplicationDbContext _dbContext;

public PermissionService(IApplicationDbContext dbContext)
{
_dbContext = dbContext;
}

public async Task<IEnumerable<Permission>> GetPermissionsAsync(Guid userId)
{
return await _dbContext.Users
.Where(a => a.Id == userId)
.SelectMany(a => a.Roles)
.SelectMany(r => r.Permissions)
.DistinctBy(p => p.Id)
.ToListAsync();
}

public async Task<bool> HasPermissionAsync(Guid userId, Guid permissionId)
{
return await _dbContext.Users
.Where(a => a.Id == userId)
.SelectMany(a => a.Roles)
.AnyAsync(r => r.Permissions.Any(p => p.Id == permissionId));
}

public async Task<bool> HasPermissionAsync(Guid userId, Permission permission) => await HasPermissionAsync(userId, permission.Id);

public async Task<bool> HasAnyPermissionsAsync(Guid userId, IEnumerable<Guid> permissionIds)
{
return await _dbContext.Users
.Where(a => a.Id == userId)
.SelectMany(a => a.Roles)
.AnyAsync(r => r.Permissions.Any(p => permissionIds.Contains(p.Id)));
}

public async Task<bool> HasAnyPermissionsAsync(Guid userId, IEnumerable<Permission> permissions) => await HasAnyPermissionsAsync(userId, permissions.Select(p => p.Id));
}
11 changes: 0 additions & 11 deletions Application/Common/Constants/AuthorizationConstants.cs

This file was deleted.

4 changes: 3 additions & 1 deletion Application/Data/IApplicationDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ public interface IApplicationDbContext
public DbSet<Category> Categories { get; set; }
public DbSet<Revision> Revisions { get; set; }
public DbSet<Review> Reviews { get; set; }
public DbSet<Author> Authors { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<Navigation> Navigations { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<Permission> Permissions { get; set; }

Task<int> SaveChangesAsync(CancellationToken token = default);
}
12 changes: 8 additions & 4 deletions Application/Extensions/ServiceCollectionExt.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Reflection;
using Application.Common.Messaging;
using Application.Authorization;
using Application.Authorization.Abstractions;
using Application.Features.Articles.CreateArticle;
using Application.Features.Articles.DeleteArticle;
using Application.Features.Articles.EditArticle;
Expand All @@ -11,15 +13,15 @@
using Application.Features.Articles.ReviewRevision;
using Application.Features.Articles.SearchArticles;
using Application.Features.Articles.SetRedirect;
using Application.Features.Authors.CreateAuthor;
using Application.Features.Authors.EditAuthor;
using Application.Features.Categories.CreateCategory;
using Application.Features.Categories.DeleteCategory;
using Application.Features.Categories.GetCategories;
using Application.Features.Categories.GetCategoriesTree;
using Application.Features.Categories.GetCategoryArticles;
using Application.Features.Navigations.GetNavigationTree;
using Application.Features.Navigations.UpdateNavigationsTree;
using Application.Features.Users.GetUser;
using Application.Features.Users.RenameUser;
using FluentValidation;
using Microsoft.Extensions.DependencyInjection;
using Slugify;
Expand All @@ -33,6 +35,8 @@ public static IServiceCollection AddApplicationServices(this IServiceCollection
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
services.AddTransient<ISlugHelper, SlugHelperForNonAsciiLanguages>();

services.AddScoped<IPermissionService, PermissionService>();

services.AddScoped<ICommandHandler<CreateArticleCommand, CreateArticleResponse>, CreateArticleCommandHandler>();
services.AddScoped<ICommandHandler<DeleteArticleCommand, DeleteArticleResponse>, DeleteArticleCommandHandler>();
services.AddScoped<ICommandHandler<EditArticleCommand, EditArticleResponse>, EditArticleCommandHandler>();
Expand All @@ -45,8 +49,8 @@ public static IServiceCollection AddApplicationServices(this IServiceCollection
services.AddScoped<IQueryHandler<SearchArticlesQuery, SearchArticlesResponse>, SearchArticlesQueryHandler>();
services.AddScoped<ICommandHandler<SetRedirectCommand, SetRedirectResponse>, SetRedirectCommandHandler>();

services.AddScoped<ICommandHandler<CreateAuthorCommand, CreateAuthorResponse>, CreateAuthorCommandHandler>();
services.AddScoped<ICommandHandler<EditAuthorCommand, EditAuthorResponse>, EditAuthorCommandHandler>();
services.AddScoped<ICommandHandler<RenameUserCommand, RenameUserResponse>, RenameUserCommandHandler>();
services.AddScoped<IQueryHandler<GetUserQuery, GetUserResponse>, GetUserQueryHandler>();

services.AddScoped<ICommandHandler<CreateCategoryCommand, CreateCategoryResponse>, CreateCategoryCommandHandler>();
services.AddScoped<ICommandHandler<DeleteCategoryCommand, DeleteCategoryResponse>, DeleteCategoryCommandHandler>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Application.Features.Articles.CreateArticle;
public class CreateArticleCommandHandler(
IApplicationDbContext dbContext,
ISlugHelper slugHelper,
ICurrentUserService identityService,
IUserContext userContext,
IValidator<CreateArticleCommand> validator
) : ICommandHandler<CreateArticleCommand, CreateArticleResponse>
{
Expand Down Expand Up @@ -47,7 +47,7 @@ public async Task<ErrorOr<CreateArticleResponse>> HandleAsync(CreateArticleComma
Id = default!,
ArticleId = id,
Article = default!,
AuthorId = identityService.UserId!,
AuthorId = userContext.UserId!.Value,
Author = default!,
AuthorsNote = command.AuthorsNote,
Content = command.Content,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Application.Features.Articles.EditArticle;

public class EditArticleCommandHandler(
IApplicationDbContext dbContext,
ICurrentUserService identityService,
IUserContext userContext,
IValidator<EditArticleCommand> validator
) : ICommandHandler<EditArticleCommand, EditArticleResponse>
{
Expand All @@ -38,7 +38,7 @@ public async Task<ErrorOr<EditArticleResponse>> HandleAsync(EditArticleCommand r
Id = default!,
ArticleId = article.Id,
Article = default!,
AuthorId = identityService.UserId!,
AuthorId = userContext.UserId!.Value,
Author = default!,
AuthorsNote = request.AuthorsNote,
Content = request.Content,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Application.Common.Utils;
using Application.Data;
using Domain.Entities;
using Domain.Rbac;
using ErrorOr;
using FluentValidation;
using Microsoft.EntityFrameworkCore;
Expand All @@ -13,7 +14,8 @@

public class GetArticleQueryHandler(
IApplicationDbContext dbContext,
IIdentityService identityService,
IUserContext userContext,
IPermissionService permissionService,
IValidator<GetArticleQuery> validator,
ICacheService cacheService
) : IQueryHandler<GetArticleQuery, GetArticleResponse>
Expand All @@ -40,7 +42,7 @@
var article = await dbContext.Articles
.Include(e => e.CurrentRevision)
.Include(e => e.RedirectArticle)
.ThenInclude(e => e.CurrentRevision)

Check warning on line 45 in Application/Features/Articles/GetArticle/GetArticleQueryHandler.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Check warning on line 45 in Application/Features/Articles/GetArticle/GetArticleQueryHandler.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
.AsNoTracking()
.FirstOrDefaultAsync(e => e.Id == id, token);

Expand All @@ -66,13 +68,14 @@

private async Task<ErrorOr<GetArticleResponse>> GetByRevisionId(Guid id, CancellationToken token)
{
var isInRole = await identityService.IsInRoleAsync(AuthorizationConstants.Roles.Editor) ||
await identityService.IsInRoleAsync(AuthorizationConstants.Roles.Admin);
var canSeePendingRevisions =
await permissionService.HasPermissionAsync(userContext.UserId!.Value, Permissions.ArticleSeePendingRevisions);

var revision = await dbContext.Revisions
.Include(e => e.LatestReview)
.Include(e => e.Article)
.AsNoTracking()
.FirstOrDefaultAsync(e => e.Id == id && (isInRole || e.LatestReview != null), token);
.FirstOrDefaultAsync(e => e.Id == id && (canSeePendingRevisions || e.LatestReview != null), token);

if (revision == null) return Errors.Revision.NotFound;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public record GetArticleResponse(
List<GetArticleResponse.Category> Categories
)
{
public record Author(string Id, string Name);
public record Author(Guid Id, string Name);

public record Category(string Id, string Name);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ public record Element(
DateTime Timestamp
);

public record Author(string Id, string Name);
public record Author(Guid Id, string Name);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public record Element(
Review? LatestReview
);

public record Author(string Id, string Name);
public record Author(Guid Id, string Name);

public record Review(
Guid Id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ public record Element(
DateTime Timestamp
);

public record Reviewer(string Id, string Name);
public record Reviewer(Guid Id, string Name);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

public class ReviewRevisionCommandHandler(
IApplicationDbContext dbContext,
ICurrentUserService identityService,
IUserContext userContext,
ICacheService cacheService,
IValidator<ReviewRevisionCommand> validator
) : ICommandHandler<ReviewRevisionCommand, ReviewRevisionResponse>
Expand All @@ -30,7 +30,7 @@
var revision = await dbContext.Revisions
.Include(e => e.Article)
.ThenInclude(e => e.CurrentRevision)
.ThenInclude(e => e.Categories)

Check warning on line 33 in Application/Features/Articles/ReviewRevision/ReviewRevisionCommandHandler.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Check warning on line 33 in Application/Features/Articles/ReviewRevision/ReviewRevisionCommandHandler.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
.Include(e => e.Categories)
.Include(e => e.LatestReview)
.FirstOrDefaultAsync(e => e.Id == command.RevisionId, token);
Expand All @@ -42,7 +42,7 @@
var review = new Review
{
Id = default!,
ReviewerId = identityService.UserId!,
ReviewerId = userContext.UserId!.Value,
Reviewer = default!,
Status = command.Status,
Message = command.Review,
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

5 changes: 0 additions & 5 deletions Application/Features/Authors/EditAuthor/EditAuthorCommand.cs

This file was deleted.

Loading
Loading