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
@@ -198,7 +198,20 @@ public class Channel
DateTimeOffset endsAtUtc
)
{
var ovr = ProgrammingOverride.Create(Id, mode, startsAtUtc, endsAtUtc);
var ovr = ProgrammingOverride.CreateOneTime(Id, mode, startsAtUtc, endsAtUtc);
_overrides.Add(ovr);
return ovr;
}
/// <summary>Еженедельный override: день недели (0=Вс..6=Сб) + окно минут суток (UTC).</summary>
public ProgrammingOverride AddWeeklyOverride(
OverrideMode mode,
int dayOfWeek,
int startMinute,
int endMinute
)
{
var ovr = ProgrammingOverride.CreateWeekly(Id, mode, dayOfWeek, startMinute, endMinute);
_overrides.Add(ovr);
return ovr;
}
@@ -0,0 +1,11 @@
namespace TeleWave.Domain.Broadcast;
/// <summary>Как повторяется override программирования канала.</summary>
public enum OverrideRecurrence
{
/// <summary>Разовое окно [StartsAtUtc, EndsAtUtc).</summary>
OneTime,
/// <summary>Еженедельно в заданный день недели на окне часов суток (UTC).</summary>
Weekly,
}
@@ -1,9 +1,12 @@
namespace TeleWave.Domain.Broadcast;
/// <summary>
/// Временный override программирования канала на окне [<see cref="StartsAtUtc"/>,
/// <see cref="EndsAtUtc"/>). Марафон = <see cref="OverrideMode.Exclusive"/> с одним шоу и большим
/// временным блоком. Пересекающийся с генерируемым временем override заменяет базовую ротацию.
/// Временный override программирования канала. Разовый (<see cref="OverrideRecurrence.OneTime"/>) —
/// на окне [<see cref="StartsAtUtc"/>, <see cref="EndsAtUtc"/>). Еженедельный
/// (<see cref="OverrideRecurrence.Weekly"/>) — каждую неделю в <see cref="DayOfWeek"/> на окне минут
/// суток [<see cref="StartMinute"/>, <see cref="EndMinute"/>) в UTC. Марафон = обычно
/// <see cref="OverrideMode.Exclusive"/> с одним шоу; пересекающийся с генерируемым временем override
/// заменяет базовую ротацию.
/// </summary>
public class ProgrammingOverride
{
@@ -12,14 +15,23 @@ public class ProgrammingOverride
public Guid Id { get; private set; }
public Guid ChannelId { get; private set; }
public OverrideMode Mode { get; private set; }
public DateTimeOffset StartsAtUtc { get; private set; }
public DateTimeOffset EndsAtUtc { get; private set; }
public OverrideRecurrence Recurrence { get; private set; }
// ── OneTime ──
public DateTimeOffset? StartsAtUtc { get; private set; }
public DateTimeOffset? EndsAtUtc { get; private set; }
// ── Weekly ── (день недели 0=Вс..6=Сб как System.DayOfWeek/JS getDay; минуты суток 0..1440, UTC)
public int? DayOfWeek { get; private set; }
public int? StartMinute { get; private set; }
public int? EndMinute { get; private set; }
public IReadOnlyList<OverrideShow> Shows => _shows;
private ProgrammingOverride() { }
internal static ProgrammingOverride Create(
internal static ProgrammingOverride CreateOneTime(
Guid channelId,
OverrideMode mode,
DateTimeOffset startsAtUtc,
@@ -30,10 +42,29 @@ public class ProgrammingOverride
Id = Guid.NewGuid(),
ChannelId = channelId,
Mode = mode,
Recurrence = OverrideRecurrence.OneTime,
StartsAtUtc = startsAtUtc,
EndsAtUtc = endsAtUtc,
};
internal static ProgrammingOverride CreateWeekly(
Guid channelId,
OverrideMode mode,
int dayOfWeek,
int startMinute,
int endMinute
) =>
new()
{
Id = Guid.NewGuid(),
ChannelId = channelId,
Mode = mode,
Recurrence = OverrideRecurrence.Weekly,
DayOfWeek = dayOfWeek,
StartMinute = startMinute,
EndMinute = endMinute,
};
public OverrideShow AddShow(Guid showId, int weight)
{
var entry = OverrideShow.Create(Id, showId, weight);
@@ -41,5 +72,18 @@ public class ProgrammingOverride
return entry;
}
public bool Covers(DateTimeOffset moment) => moment >= StartsAtUtc && moment < EndsAtUtc;
/// <summary>Действует ли override в этот момент (по типу повторения).</summary>
public bool Covers(DateTimeOffset moment)
{
if (Recurrence == OverrideRecurrence.Weekly)
{
var utc = moment.UtcDateTime;
var minuteOfDay = utc.Hour * 60 + utc.Minute;
return (int)utc.DayOfWeek == DayOfWeek
&& minuteOfDay >= StartMinute
&& minuteOfDay < EndMinute;
}
return moment >= StartsAtUtc && moment < EndsAtUtc;
}
}
@@ -205,7 +205,7 @@ public static class SchedulePlanner
IReadOnlyDictionary<Guid, PlannerShow> byShowId
)
{
var ovr = input.Overrides.FirstOrDefault(o => moment >= o.StartsAtUtc && moment < o.EndsAtUtc);
var ovr = input.Overrides.FirstOrDefault(o => o.Covers(moment));
if (ovr is not null)
{
var overridden = new List<(PlannerShow, int)>();
@@ -19,13 +19,36 @@ public sealed record PlannerHourWindow(int StartHour, int EndHour)
public bool Contains(int hour) => hour >= StartHour && hour < EndHour;
}
/// <summary>Override в терминах планировщика: окно + режим + шоу с весами.</summary>
/// <summary>
/// Override в терминах планировщика: режим + шоу с весами + правило действия (разовое окно либо
/// еженедельно по дню недели на окне минут суток UTC).
/// </summary>
public sealed record PlannerOverride(
DateTimeOffset StartsAtUtc,
DateTimeOffset EndsAtUtc,
OverrideMode Mode,
IReadOnlyList<PlannerOverrideShow> Shows
);
IReadOnlyList<PlannerOverrideShow> Shows,
OverrideRecurrence Recurrence = OverrideRecurrence.OneTime,
DateTimeOffset? StartsAtUtc = null,
DateTimeOffset? EndsAtUtc = null,
int? DayOfWeek = null,
int? StartMinute = null,
int? EndMinute = null
)
{
/// <summary>Действует ли override в этот момент.</summary>
public bool Covers(DateTimeOffset moment)
{
if (Recurrence == OverrideRecurrence.Weekly)
{
var utc = moment.UtcDateTime;
var minuteOfDay = utc.Hour * 60 + utc.Minute;
return (int)utc.DayOfWeek == DayOfWeek
&& minuteOfDay >= StartMinute
&& minuteOfDay < EndMinute;
}
return moment >= StartsAtUtc && moment < EndsAtUtc;
}
}
public sealed record PlannerOverrideShow(Guid ShowId, int Weight);