Add password reset functionality for admin users: implement ResetPassword endpoint, update AdminUserEndpoints to include password reset logic, and enhance user interface for password management. Update translations for new features and ensure proper error handling in the reset process.

This commit is contained in:
Leonid Pershin
2026-07-25 19:49:08 +03:00
parent d44aa44d95
commit 1f6fa6f1ae
30 changed files with 1755 additions and 58 deletions
@@ -0,0 +1,7 @@
using LiteCqrs;
using TeleWave.Application.Common.Models;
namespace TeleWave.Application.Admin.Users.ResetPassword;
/// <summary>Сброс пароля пользователя администратором (без текущего пароля).</summary>
public sealed record ResetUserPasswordCommand(Guid UserId, string NewPassword) : ICommand<Result>;
@@ -0,0 +1,14 @@
using LiteCqrs;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Common.Models;
namespace TeleWave.Application.Admin.Users.ResetPassword;
public sealed class ResetUserPasswordCommandHandler(IIdentityService identityService)
: ICommandHandler<ResetUserPasswordCommand, Result>
{
public Task<Result> Handle(
ResetUserPasswordCommand command,
CancellationToken cancellationToken
) => identityService.ResetPasswordAsync(command.UserId, command.NewPassword, cancellationToken);
}
@@ -0,0 +1,13 @@
using FluentValidation;
namespace TeleWave.Application.Admin.Users.ResetPassword;
public sealed class ResetUserPasswordCommandValidator : AbstractValidator<ResetUserPasswordCommand>
{
public ResetUserPasswordCommandValidator()
{
// Длина — здесь; сложность (цифра/заглавная) проверяют валидаторы ASP.NET Identity при сбросе.
RuleFor(x => x.UserId).NotEmpty();
RuleFor(x => x.NewPassword).NotEmpty().MinimumLength(8);
}
}
@@ -27,8 +27,12 @@ public sealed record OverrideShowDto(Guid ShowId, string ShowName, int Weight);
public sealed record ProgrammingOverrideDto(
Guid Id,
OverrideMode Mode,
DateTimeOffset StartsAtUtc,
DateTimeOffset EndsAtUtc,
OverrideRecurrence Recurrence,
DateTimeOffset? StartsAtUtc,
DateTimeOffset? EndsAtUtc,
int? DayOfWeek,
int? StartMinute,
int? EndMinute,
IReadOnlyList<OverrideShowDto> Shows
);
@@ -7,7 +7,11 @@ namespace TeleWave.Application.Broadcast.CreateOverride;
public sealed record CreateProgrammingOverrideCommand(
Guid ChannelId,
OverrideMode Mode,
DateTimeOffset StartsAtUtc,
DateTimeOffset EndsAtUtc,
OverrideRecurrence Recurrence,
DateTimeOffset? StartsAtUtc,
DateTimeOffset? EndsAtUtc,
int? DayOfWeek,
int? StartMinute,
int? EndMinute,
IReadOnlyList<OverrideShowInput> Shows
) : ICommand<Result<Guid>>;
@@ -2,6 +2,7 @@ using LiteCqrs;
using Microsoft.EntityFrameworkCore;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Common.Models;
using TeleWave.Domain.Broadcast;
namespace TeleWave.Application.Broadcast.CreateOverride;
@@ -13,8 +14,24 @@ public sealed class CreateProgrammingOverrideCommandHandler(IAppDbContext dbCont
CancellationToken cancellationToken
)
{
if (command.EndsAtUtc <= command.StartsAtUtc)
var weekly = command.Recurrence == OverrideRecurrence.Weekly;
if (weekly)
{
if (
command.DayOfWeek is not (>= 0 and <= 6)
|| command.StartMinute is not { } sm
|| command.EndMinute is not { } em
|| em <= sm
|| sm < 0
|| em > 1440
)
return Result.Failure<Guid>(ChannelErrors.InvalidOverrideWindow);
}
else if (command.StartsAtUtc is not { } start || command.EndsAtUtc is not { } end || end <= start)
{
return Result.Failure<Guid>(ChannelErrors.InvalidOverrideWindow);
}
if (command.Shows.Count == 0)
return Result.Failure<Guid>(ChannelErrors.OverrideNeedsShow);
@@ -33,7 +50,14 @@ public sealed class CreateProgrammingOverrideCommandHandler(IAppDbContext dbCont
if (existingCount != showIds.Count)
return Result.Failure<Guid>(ChannelErrors.ShowNotFound);
var ovr = channel.AddOverride(command.Mode, command.StartsAtUtc, command.EndsAtUtc);
var ovr = weekly
? channel.AddWeeklyOverride(
command.Mode,
command.DayOfWeek!.Value,
command.StartMinute!.Value,
command.EndMinute!.Value
)
: channel.AddOverride(command.Mode, command.StartsAtUtc!.Value, command.EndsAtUtc!.Value);
foreach (var show in command.Shows)
ovr.AddShow(show.ShowId, show.Weight);
@@ -104,12 +104,18 @@ public sealed class GetChannelQueryHandler(IAppDbContext dbContext)
.ToList();
var overrides = channel.Overrides
.OrderBy(o => o.StartsAtUtc)
.OrderBy(o => o.Recurrence)
.ThenBy(o => o.StartsAtUtc)
.ThenBy(o => o.DayOfWeek)
.Select(o => new ProgrammingOverrideDto(
o.Id,
o.Mode,
o.Recurrence,
o.StartsAtUtc,
o.EndsAtUtc,
o.DayOfWeek,
o.StartMinute,
o.EndMinute,
o.Shows
.Select(s => new OverrideShowDto(s.ShowId, ShowName(s.ShowId), s.Weight))
.ToList()
@@ -562,10 +562,14 @@ public sealed class ScheduleGenerator(
var overrides = channel.Overrides
.Select(o => new PlannerOverride(
o.Mode,
o.Shows.Select(s => new PlannerOverrideShow(s.ShowId, s.Weight)).ToList(),
o.Recurrence,
o.StartsAtUtc,
o.EndsAtUtc,
o.Mode,
o.Shows.Select(s => new PlannerOverrideShow(s.ShowId, s.Weight)).ToList()
o.DayOfWeek,
o.StartMinute,
o.EndMinute
))
.ToList();
@@ -36,6 +36,13 @@ public interface IIdentityService
CancellationToken cancellationToken
);
/// <summary>Сброс пароля администратором — без текущего пароля (для чужого аккаунта).</summary>
Task<Result> ResetPasswordAsync(
Guid userId,
string newPassword,
CancellationToken cancellationToken
);
Task<Result> ChangeUserNameAsync(
Guid userId,
string newUserName,