Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ARPA-951 added userstatus query parameter #830

Merged
merged 4 commits into from
Nov 18, 2023
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
4 changes: 2 additions & 2 deletions Orso.Arpa.Api/Controllers/UsersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ public async Task<ActionResult> Delete([FromRoute] string username)
[Authorize(Policy = AuthorizationPolicies.AtLeastStaffPolicy)]
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IEnumerable<UserDto>> Get()
public async Task<IEnumerable<UserDto>> Get([FromQuery]UserStatus? userStatus)
{
return await _userService.GetAsync();
return await _userService.GetAsync(userStatus);
}

/// <summary>
Expand Down
3 changes: 2 additions & 1 deletion Orso.Arpa.Api/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
Expand Down Expand Up @@ -164,7 +165,7 @@ public void ConfigureServices(IServiceCollection services)
{
options.JsonSerializerOptions.Converters.Add(new DateTimeJsonConverter());
options.JsonSerializerOptions.Converters.Add(new TrimmedStringConverter());
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicies.SnakeCaseUpper)); // https://github.com/dotnet/runtime/issues/782
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicies.SnakeCaseUpper)); // https://github.com/dotnet/runtime/issues/782 will be included in Text.Json in .NET 8
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
})
.AddApplicationPart(typeof(Startup).Assembly)
Expand Down
2 changes: 1 addition & 1 deletion Orso.Arpa.Api/Workers/BirthdayWorker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
}
catch (Exception e)
{
_logger.LogError(e, "{prefix} An erroc occured while sending birthday e-mail for {person}", person);
_logger.LogError(e, "{prefix} An erroc occured while sending birthday e-mail for {person}", LoggerPrefix, person);
}

}
Expand Down
10 changes: 5 additions & 5 deletions Orso.Arpa.Api/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,17 @@
}
},
"rules": {
"01_SkipNonCriticalMicrosoftLogs": {
"A_SkipNonCriticalMicrosoftLogs": {
"logger": "Microsoft.*",
"maxLevel": "Info",
"final": true
},
"02_SkipNonCriticalSystemNetHttpLogs": {
"B_SkipNonCriticalSystemNetHttpLogs": {
"logger": "System.Net.Http.*",
"maxLevel": "Info",
"final": true
},
"03_ApplicationInsightsLogs": {
"C_ApplicationInsightsLogs": {
"logger": "*",
"minLevel": "Info",
"writeTo": "applicationInsightsTarget",
Expand All @@ -84,7 +84,7 @@
}

},
"04_SlackInfoLog": {
"D_SlackInfoLog": {
"logger": "*",
"level": "Info",
"writeTo": "slackInfoTarget",
Expand All @@ -95,7 +95,7 @@
}
}
},
"05_SlackErrorLog": {
"E_SlackErrorLog": {
"logger": "*",
"minLevel": "Error",
"writeTo": "slackErrorTarget"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Orso.Arpa.Application.UserApplication.Model;
using Orso.Arpa.Domain.UserDomain.Enums;

namespace Orso.Arpa.Application.UserApplication.Interfaces
{
public interface IUserService
{
Task<IEnumerable<UserDto>> GetAsync();
Task<IEnumerable<UserDto>> GetAsync(UserStatus? userStatus);

Task<UserDto> GetByIdAsync(Guid id);

Expand Down
29 changes: 26 additions & 3 deletions Orso.Arpa.Application/UserApplication/Model/UserDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using Orso.Arpa.Application.SectionApplication.Model;
using Orso.Arpa.Domain.UserDomain.Enums;
using Orso.Arpa.Domain.UserDomain.Model;

namespace Orso.Arpa.Application.UserApplication.Model
Expand All @@ -15,7 +17,9 @@ public class UserDto
public string Email { get; set; }
public bool EmailConfirmed { get; set; }
public DateTime? CreatedAt { get; set; }
public IList<Guid> StakeholderGroupIds { get; set; } = new List<Guid>();
public IList<SectionDto> StakeholderGroups { get; set; } = new List<SectionDto>();
public UserStatus Status { get; set; }
public Guid PersonId { get; set; }
}

public class UserDtoMappingProfile : Profile
Expand All @@ -24,8 +28,27 @@ public UserDtoMappingProfile()
{
CreateMap<User, UserDto>()
.ForMember(dest => dest.CreatedAt, opt => opt.MapFrom(src => src.CreatedAt))
.ForMember(dest => dest.StakeholderGroupIds, opt => opt.MapFrom(src => src.Person.StakeholderGroups.Select(g => g.SectionId)))
.ForMember(dest => dest.RoleNames, opt => opt.Ignore());
.ForMember(dest => dest.EmailConfirmed, opt => opt.MapFrom(src => src.EmailConfirmed))
.ForMember(dest => dest.PersonId, opt => opt.MapFrom(src => src.PersonId))
.ForMember(dest => dest.StakeholderGroups, opt => opt.MapFrom(src => src.Person.StakeholderGroups.Select(g => g.Section)))
.ForMember(dest => dest.Status, opt => opt.MapFrom<UserStatusResolver>())
.ForMember(dest => dest.RoleNames, opt => opt.MapFrom(src => src.UserRoles.Select(ur => ur.Role.Name)));
}
}

public class UserStatusResolver : IValueResolver<User, UserDto, UserStatus>
{
public UserStatus Resolve(User source, UserDto destination, UserStatus member, ResolutionContext context)
{
if (!source.EmailConfirmed)
{
return UserStatus.AwaitingEmailConfirmation;
}
if (!source.UserRoles.Any())
{
return UserStatus.AwaitingRoleAssignment;
}
return UserStatus.Active;
}
}
}
20 changes: 7 additions & 13 deletions Orso.Arpa.Application/UserApplication/Services/UserService.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;
using MediatR;
using Orso.Arpa.Application.UserApplication.Interfaces;
using Orso.Arpa.Application.UserApplication.Model;
using Orso.Arpa.Domain.UserDomain.Commands;
using Orso.Arpa.Domain.UserDomain.Enums;
using Orso.Arpa.Domain.UserDomain.Model;
using Orso.Arpa.Domain.UserDomain.Queries;

Expand All @@ -24,24 +26,16 @@ public UserService(
_mapper = mapper;
}

public async Task<IEnumerable<UserDto>> GetAsync()
public async Task<IEnumerable<UserDto>> GetAsync(UserStatus? userStatus)
{
IEnumerable<User> users = await _mediator.Send(new ListUsers.Query());

var dtos = new List<UserDto>();
foreach (User user in users)
{
UserDto dto = _mapper.Map<UserDto>(user);
dto.RoleNames = await _mediator.Send(new ListUserRoles.Query(user));
dtos.Add(dto);
}

return dtos;
IList<User> users = await _mediator.Send(new ListUsers.Query(userStatus));
return _mapper.Map<IEnumerable<UserDto>>(users);
}

public async Task<UserDto> GetByIdAsync(Guid id)
{
return _mapper.Map<UserDto>(await _mediator.Send(new GetUser.Query(id)));
User user = await _mediator.Send(new GetUser.Query(id));
return _mapper.Map<UserDto>(user);
}

public async Task DeleteAsync(string userName)
Expand Down
2 changes: 1 addition & 1 deletion Orso.Arpa.Domain/UserDomain/Commands/ModifyMyUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public async Task<Unit> Handle(Command request, CancellationToken cancellationTo
{
User existingUser = await _userAccessor.GetCurrentUserAsync(cancellationToken);

existingUser.Update(request);
existingUser.Update(request, _userManager.NormalizeEmail(request.Email));

IdentityResult result = await _userManager.UpdateAsync(existingUser);

Expand Down
9 changes: 9 additions & 0 deletions Orso.Arpa.Domain/UserDomain/Enums/UserStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Orso.Arpa.Domain.UserDomain.Enums
{
public enum UserStatus
{
Active = 2,
AwaitingRoleAssignment = 1,
AwaitingEmailConfirmation = 0
}
}
2 changes: 2 additions & 0 deletions Orso.Arpa.Domain/UserDomain/Model/Role.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ public class Role : IdentityRole<Guid>

[AuditLogIgnore]
public override string ConcurrencyStamp { get => base.ConcurrencyStamp; set => base.ConcurrencyStamp = value; }

public virtual ICollection<UserRole> UserRoles { get; private set; } = new HashSet<UserRole>();
}
}
5 changes: 4 additions & 1 deletion Orso.Arpa.Domain/UserDomain/Model/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ namespace Orso.Arpa.Domain.UserDomain.Model
{
public class User : IdentityUser<Guid>
{
public void Update(ModifyMyUser.Command command)
public void Update(ModifyMyUser.Command command, string normalizedEmail)
{
Email = command.Email;
NormalizedEmail = normalizedEmail;
Person.Update(command);
}

Expand All @@ -34,5 +35,7 @@ public void Update(ModifyMyUser.Command command)

[AuditLogIgnore]
public override string ConcurrencyStamp { get => base.ConcurrencyStamp; set => base.ConcurrencyStamp = value; }

public virtual ICollection<UserRole> UserRoles { get; private set; } = new HashSet<UserRole>();
}
}
15 changes: 15 additions & 0 deletions Orso.Arpa.Domain/UserDomain/Model/UserRole.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Identity;
using Orso.Arpa.Domain.General.Attributes;
using Orso.Arpa.Domain.ProjectDomain.Model;

namespace Orso.Arpa.Domain.UserDomain.Model
{
public class UserRole : IdentityUserRole<Guid>
{
public virtual User User { get; private set; }

public virtual Role Role { get; private set; }
}
}
18 changes: 8 additions & 10 deletions Orso.Arpa.Domain/UserDomain/Queries/GetUser.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Orso.Arpa.Domain.General.Errors;
using Orso.Arpa.Domain.General.Interfaces;
using Orso.Arpa.Domain.UserDomain.Model;
using Orso.Arpa.Domain.UserDomain.Repositories;

Expand All @@ -22,22 +24,18 @@ public Query(Guid id)

public class Handler : IRequestHandler<Query, User>
{
private readonly ArpaUserManager _arpaUserManager;
private readonly IArpaContext _arpaContext;

public Handler(
ArpaUserManager arpaUserManager)

public Handler(IArpaContext arpaContext)
{
_arpaUserManager = arpaUserManager;
_arpaContext = arpaContext;
}

public async Task<User> Handle(Query request, CancellationToken cancellationToken)
{
User user = await _arpaUserManager.FindByIdAsync(request.Id, cancellationToken);
if (user is null)
{
throw new NotFoundException(nameof(User), nameof(Query.Id));
}
return user;
User user = await _arpaContext.FindAsync<User>(new object[] { request.Id }, cancellationToken);
return user ?? throw new NotFoundException(nameof(User), nameof(Query.Id));
}
}
}
Expand Down
45 changes: 36 additions & 9 deletions Orso.Arpa.Domain/UserDomain/Queries/ListUsers.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Orso.Arpa.Domain.General.Interfaces;
using Orso.Arpa.Domain.UserDomain.Enums;
using Orso.Arpa.Domain.UserDomain.Model;
using Orso.Arpa.Domain.UserDomain.Repositories;

namespace Orso.Arpa.Domain.UserDomain.Queries
{
public static class ListUsers
{
public class Query : IRequest<IEnumerable<User>> { }
public class Query : IRequest<IList<User>>
{
public UserStatus? UserStatus { get; set; }

public Query(UserStatus? userStatus)
{
UserStatus = userStatus;
}
}

public class Handler : IRequestHandler<Query, IEnumerable<User>>
public class Handler : IRequestHandler<Query, IList<User>>
{
private readonly ArpaUserManager _userManager;
private readonly IArpaContext _arpaContext;


public Handler(
ArpaUserManager userManager)
public Handler(IArpaContext arpaContext)
{
_userManager = userManager;
_arpaContext = arpaContext;
}

public Task<IEnumerable<User>> Handle(Query request, CancellationToken cancellationToken)
public async Task<IList<User>> Handle(Query request, CancellationToken cancellationToken)
{
return Task.FromResult(_userManager.Users.Include(u => u.Person).ToList() as IEnumerable<User>);
IQueryable<User> users = _arpaContext.Users;

switch (request.UserStatus)
{
case UserStatus.Active:
users = users.Where(u => u.EmailConfirmed && u.UserRoles.Any());
break;
case UserStatus.AwaitingEmailConfirmation:
users = users.Where(u => !u.EmailConfirmed);
break;
case UserStatus.AwaitingRoleAssignment:
users = users.Where(u => u.EmailConfirmed && !u.UserRoles.Any());
break;
default:
break;
}

return await users.ToListAsync(cancellationToken);
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions Orso.Arpa.Domain/_General/Interfaces/IArpaContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Orso.Arpa.Domain.RegionDomain.Model;
using Orso.Arpa.Domain.SectionDomain.Model;
using Orso.Arpa.Domain.SelectValueDomain.Model;
using Orso.Arpa.Domain.UserDomain.Model;
using Orso.Arpa.Domain.VenueDomain.Model;

namespace Orso.Arpa.Domain.General.Interfaces
Expand Down Expand Up @@ -48,6 +49,8 @@ public interface IArpaContext
DbSet<Localization> Localizations { get; set; }
DbSet<News> News { get; set; }

DbSet<User> Users { get; set; }

DbSet<MusicianProfileDocument> MusicianProfileDocuments { get; set; }
DbSet<MusicianProfileDeactivation> MusicianProfileDeactivations { get; set; }

Expand Down
3 changes: 2 additions & 1 deletion Orso.Arpa.Persistence/DataAccess/ArpaContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
Expand Down Expand Up @@ -34,7 +35,7 @@

namespace Orso.Arpa.Persistence.DataAccess
{
public class ArpaContext : IdentityDbContext<User, Role, Guid>, IArpaContext
public class ArpaContext : IdentityDbContext<User, Role, Guid, IdentityUserClaim<Guid>, UserRole, IdentityUserLogin<Guid>, IdentityRoleClaim<Guid>, IdentityUserToken<Guid>>, IArpaContext
{
private readonly ITokenAccessor _tokenAccessor;
private readonly IDateTimeProvider _dateTimeProvider;
Expand Down Expand Up @@ -256,7 +257,7 @@
AuditLogs.AddRange(auditLogs);
}

private AuditLog createAuditTrailEntry(string currentUserDisplayName, EntityEntry entry, Type entityType)

Check warning on line 260 in Orso.Arpa.Persistence/DataAccess/ArpaContext.cs

View workflow job for this annotation

GitHub Actions / Build and run sonar analysis

Refactor this method to reduce its Cognitive Complexity from 23 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)
{
var auditEntry = new AuditLog()
{
Expand Down Expand Up @@ -368,7 +369,7 @@
{
Database.OpenConnection();
using DbCommand command = Database.GetDbConnection().CreateCommand();
command.CommandText = $"SELECT public.fn_is_person_eligible_for_appointment('{personId}', '{appointmentId}')";

Check warning on line 372 in Orso.Arpa.Persistence/DataAccess/ArpaContext.cs

View workflow job for this annotation

GitHub Actions / Build and run sonar analysis

Make sure using a dynamically formatted SQL query is safe here. (https://rules.sonarsource.com/csharp/RSPEC-2077)
return (bool)command.ExecuteScalar();
}

Expand Down
Loading
Loading