feat(bot): add online/offline wizard locations
PR Checks / test-and-build (pull_request) Successful in 15m52s
PR Checks / test-and-build (pull_request) Successful in 15m52s
Add format and location steps to the Telegram /newsession wizard, persist offline addresses in sessions.location_address, and render online links/offline addresses in schedule messages. Bump version to 3.10.0.
This commit is contained in:
@@ -70,7 +70,7 @@ public sealed class CancelSessionHandler(
|
||||
|
||||
// 3. Загружаем весь батч для перерисовки
|
||||
var batchSessions = await connection.QueryAsync<SessionBatchDto>(
|
||||
@"SELECT id as SessionId, scheduled_at as ScheduledAt, status as Status, max_players as MaxPlayers, join_link as JoinLink
|
||||
@"SELECT id as SessionId, scheduled_at as ScheduledAt, status as Status, max_players as MaxPlayers, join_link as JoinLink, format as Format, location_address as LocationAddress
|
||||
FROM sessions
|
||||
WHERE batch_id = @BatchId
|
||||
ORDER BY scheduled_at",
|
||||
|
||||
@@ -293,15 +293,16 @@ public sealed class CreateSessionHandler
|
||||
User: user,
|
||||
Group: group,
|
||||
Title: p.Title ?? string.Empty,
|
||||
Link: string.Empty,
|
||||
Link: p.Format == WizardSessionFormat.Online ? p.JoinLink ?? string.Empty : string.Empty,
|
||||
ScheduledTimes: scheduledTimes,
|
||||
MaxPlayers: maxPlayers,
|
||||
ImageReference: p.ImageFileId ?? p.ImageUrl,
|
||||
System: ParseSystem(p.System),
|
||||
Description: p.Description,
|
||||
Format: null,
|
||||
Format: p.Format?.ToString(),
|
||||
DurationMinutes: p.DurationMinutes,
|
||||
IsOneShot: isOneShot);
|
||||
IsOneShot: isOneShot,
|
||||
LocationAddress: p.Format == WizardSessionFormat.Offline ? p.LocationAddress : null);
|
||||
}
|
||||
|
||||
private static GameSystem? ParseSystem(string? code)
|
||||
@@ -317,6 +318,9 @@ public sealed class CreateSessionHandler
|
||||
if (string.IsNullOrWhiteSpace(p.Title)) missingFields.Add("название");
|
||||
if (string.IsNullOrWhiteSpace(p.System)) missingFields.Add("система");
|
||||
if (!p.DurationMinutes.HasValue) missingFields.Add("длительность");
|
||||
if (p.Format is null) missingFields.Add("формат");
|
||||
if (p.Format == WizardSessionFormat.Online && string.IsNullOrWhiteSpace(p.JoinLink)) missingFields.Add("ссылка");
|
||||
if (p.Format == WizardSessionFormat.Offline && string.IsNullOrWhiteSpace(p.LocationAddress)) missingFields.Add("адрес");
|
||||
if (p.Visibility is null) missingFields.Add("видимость");
|
||||
|
||||
if (p.Type == WizardCreationType.Single)
|
||||
|
||||
@@ -139,7 +139,9 @@ public sealed class PromoteWaitlistedPlayerHandler(
|
||||
scheduled_at AS ScheduledAt,
|
||||
status AS Status,
|
||||
max_players AS MaxPlayers,
|
||||
join_link AS JoinLink
|
||||
join_link AS JoinLink,
|
||||
format AS Format,
|
||||
location_address AS LocationAddress
|
||||
FROM sessions
|
||||
WHERE batch_id = @BatchId
|
||||
ORDER BY scheduled_at
|
||||
|
||||
+1
-1
@@ -162,7 +162,7 @@ public sealed class RescheduleVotingDeadlineService(
|
||||
await using var connection = await dataSource.OpenConnectionAsync(ct);
|
||||
|
||||
var batchSessions = (await connection.QueryAsync<SessionBatchDto>(
|
||||
"SELECT id AS SessionId, scheduled_at AS ScheduledAt, status AS Status, max_players AS MaxPlayers, join_link AS JoinLink FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at",
|
||||
"SELECT id AS SessionId, scheduled_at AS ScheduledAt, status AS Status, max_players AS MaxPlayers, join_link AS JoinLink, format AS Format, location_address AS LocationAddress FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at",
|
||||
new { result.BatchId })).ToList();
|
||||
|
||||
var batchParticipants = (await connection.QueryAsync<ParticipantBatchDto>(
|
||||
|
||||
@@ -23,7 +23,14 @@ public static class TelegramSessionBatchRenderer
|
||||
|
||||
if (!string.IsNullOrEmpty(session.JoinLink))
|
||||
{
|
||||
messageText += $"🔗 <a href=\"{System.Net.WebUtility.HtmlEncode(session.JoinLink)}\">Ссылка на игру</a>\n";
|
||||
var encodedLink = System.Net.WebUtility.HtmlEncode(session.JoinLink);
|
||||
messageText += $"🔗 Ссылка на игру: <a href=\"{encodedLink}\">{encodedLink}</a>\n";
|
||||
}
|
||||
|
||||
if (string.Equals(session.Format, "Offline", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.IsNullOrWhiteSpace(session.LocationAddress))
|
||||
{
|
||||
messageText += $"📍 Адрес: {System.Net.WebUtility.HtmlEncode(session.LocationAddress)}\n";
|
||||
}
|
||||
|
||||
if (session.ActivePlayers.Count > 0)
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE sessions
|
||||
ADD COLUMN location_address TEXT;
|
||||
@@ -145,7 +145,7 @@ public sealed class DiscordRescheduleVotingDeadlineService(
|
||||
return;
|
||||
|
||||
var sessions = (await connection.QueryAsync<SessionBatchDto>(
|
||||
"SELECT id AS SessionId, scheduled_at AS ScheduledAt, status AS Status, max_players AS MaxPlayers, join_link AS JoinLink FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at",
|
||||
"SELECT id AS SessionId, scheduled_at AS ScheduledAt, status AS Status, max_players AS MaxPlayers, join_link AS JoinLink, format AS Format, location_address AS LocationAddress FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at",
|
||||
new { result.BatchId })).ToList();
|
||||
|
||||
var participants = (await connection.QueryAsync<ParticipantBatchDto>(
|
||||
|
||||
@@ -15,4 +15,5 @@ public sealed record CreateSessionCommand(
|
||||
string? Description = null,
|
||||
string? Format = null,
|
||||
int? DurationMinutes = null,
|
||||
bool IsOneShot = false);
|
||||
bool IsOneShot = false,
|
||||
string? LocationAddress = null);
|
||||
|
||||
@@ -124,8 +124,8 @@ public sealed class CreateSessionHandler(
|
||||
{
|
||||
var sessionId = await connection.ExecuteScalarAsync<Guid>(
|
||||
"""
|
||||
INSERT INTO sessions (batch_id, group_id, title, join_link, scheduled_at, status, max_players, system, description, format, duration_minutes, is_one_shot, cover_image_url)
|
||||
VALUES (@BatchId, @GroupId, @Title, @Link, @ScheduledAt, @Status, @MaxPlayers, @System, @Description, @Format, @DurationMinutes, @IsOneShot, @CoverImageUrl)
|
||||
INSERT INTO sessions (batch_id, group_id, title, join_link, scheduled_at, status, max_players, system, description, format, duration_minutes, is_one_shot, cover_image_url, location_address)
|
||||
VALUES (@BatchId, @GroupId, @Title, @Link, @ScheduledAt, @Status, @MaxPlayers, @System, @Description, @Format, @DurationMinutes, @IsOneShot, @CoverImageUrl, @LocationAddress)
|
||||
RETURNING id;
|
||||
""",
|
||||
new
|
||||
@@ -142,11 +142,19 @@ public sealed class CreateSessionHandler(
|
||||
command.Format,
|
||||
DurationMinutes = command.DurationMinutes,
|
||||
IsOneShot = command.IsOneShot,
|
||||
CoverImageUrl = command.ImageReference
|
||||
CoverImageUrl = command.ImageReference,
|
||||
command.LocationAddress
|
||||
},
|
||||
transaction);
|
||||
|
||||
sessions.Add(new SessionBatchDto(sessionId, scheduledAt.UtcDateTime, SessionStatus.Planned, command.MaxPlayers, command.Link));
|
||||
sessions.Add(new SessionBatchDto(
|
||||
sessionId,
|
||||
scheduledAt.UtcDateTime,
|
||||
SessionStatus.Planned,
|
||||
command.MaxPlayers,
|
||||
command.Link,
|
||||
command.Format,
|
||||
command.LocationAddress));
|
||||
}
|
||||
|
||||
await transaction.CommitAsync(ct);
|
||||
|
||||
@@ -135,7 +135,7 @@ public sealed class JoinSessionHandler(
|
||||
|
||||
// Загружаем весь батч для перерисовки
|
||||
var batchSessions = await connection.QueryAsync<SessionBatchDto>(
|
||||
@"SELECT id as SessionId, scheduled_at as ScheduledAt, status as Status, max_players as MaxPlayers, join_link as JoinLink
|
||||
@"SELECT id as SessionId, scheduled_at as ScheduledAt, status as Status, max_players as MaxPlayers, join_link as JoinLink, format as Format, location_address as LocationAddress
|
||||
FROM sessions
|
||||
WHERE batch_id = @BatchId
|
||||
ORDER BY scheduled_at",
|
||||
|
||||
@@ -161,7 +161,9 @@ public sealed class LeaveSessionHandler(
|
||||
scheduled_at AS ScheduledAt,
|
||||
status AS Status,
|
||||
max_players AS MaxPlayers,
|
||||
join_link AS JoinLink
|
||||
join_link AS JoinLink,
|
||||
format AS Format,
|
||||
location_address AS LocationAddress
|
||||
FROM sessions
|
||||
WHERE batch_id = @BatchId
|
||||
ORDER BY scheduled_at
|
||||
|
||||
@@ -226,9 +226,20 @@ public sealed class GameCreationWizard
|
||||
|
||||
case WizardStepNames.Capacity when payload.Single?.MaxPlayers is null:
|
||||
return int.TryParse(input, out var cap) && cap >= WizardStepLimits.MinCapacity && cap <= WizardStepLimits.MaxCapacity
|
||||
? (WizardStepNames.Visibility, SetMaxPlayers(payload, cap), payload)
|
||||
? (WizardStepNames.Format, SetMaxPlayers(payload, cap), payload)
|
||||
: (null, "Лимит должен быть 1..50", payload);
|
||||
|
||||
case WizardStepNames.Location when payload.Format == WizardSessionFormat.Online:
|
||||
return Uri.TryCreate(input.Trim(), UriKind.Absolute, out var locationUri) &&
|
||||
(locationUri.Scheme == Uri.UriSchemeHttp || locationUri.Scheme == Uri.UriSchemeHttps)
|
||||
? (WizardStepNames.Visibility, SetJoinLink(payload, input.Trim()), payload)
|
||||
: (null, "Некорректная ссылка", payload);
|
||||
|
||||
case WizardStepNames.Location when payload.Format == WizardSessionFormat.Offline:
|
||||
return ValidateText(input, WizardStepLimits.MaxLocationLength, "Адрес не может быть пустым", "Слишком длинный адрес", out var address)
|
||||
? (WizardStepNames.Visibility, SetLocationAddress(payload, address), payload)
|
||||
: (null, address, payload);
|
||||
|
||||
case WizardStepNames.PoolSystemDuration when payload.System is null:
|
||||
return ValidateText(input, WizardStepLimits.MaxSystemLength, "Слишком длинное название системы", "Слишком длинное название системы", out var psys)
|
||||
? (WizardStepNames.PoolSystemDuration, SetSystem(payload, psys), payload)
|
||||
@@ -236,7 +247,7 @@ public sealed class GameCreationWizard
|
||||
|
||||
case WizardStepNames.PoolSystemDuration when payload.DurationMinutes is null:
|
||||
return TryParseHours(input, out var pdur)
|
||||
? (WizardStepNames.Visibility, SetDurationMinutes(payload, pdur), payload)
|
||||
? (WizardStepNames.Format, SetDurationMinutes(payload, pdur), payload)
|
||||
: (null, "Неверная длительность (1..12 ч)", payload);
|
||||
|
||||
case WizardStepNames.PoolSlotDateTime:
|
||||
@@ -264,6 +275,7 @@ public sealed class GameCreationWizard
|
||||
WizardStepNames.System => ApplySystemChoice(payload, choice),
|
||||
WizardStepNames.Duration => ApplyDurationChoice(payload, choice),
|
||||
WizardStepNames.Capacity => ApplyCapacityChoice(payload, choice),
|
||||
WizardStepNames.Format => ApplyFormatChoice(payload, choice),
|
||||
WizardStepNames.Visibility => ApplyVisibilityChoice(payload, choice),
|
||||
WizardStepNames.PickClub => ApplyPickClubChoice(payload, choice),
|
||||
WizardStepNames.Publish => ApplyPublishChoice(payload, choice),
|
||||
@@ -302,7 +314,7 @@ public sealed class GameCreationWizard
|
||||
{
|
||||
if (choice is "no_limit")
|
||||
{
|
||||
return (WizardStepNames.Visibility, SetMaxPlayers(p, null));
|
||||
return (WizardStepNames.Format, SetMaxPlayers(p, null));
|
||||
}
|
||||
|
||||
if (choice is "waitlist:on" or "waitlist:off" && p.Single?.MaxPlayers is null)
|
||||
@@ -312,12 +324,19 @@ public sealed class GameCreationWizard
|
||||
|
||||
return choice switch
|
||||
{
|
||||
"waitlist:on" => (WizardStepNames.Visibility, SetWaitlist(p, true)),
|
||||
"waitlist:off" => (WizardStepNames.Visibility, SetWaitlist(p, false)),
|
||||
"waitlist:on" => (WizardStepNames.Format, SetWaitlist(p, true)),
|
||||
"waitlist:off" => (WizardStepNames.Format, SetWaitlist(p, false)),
|
||||
_ => (null, "Неизвестный выбор"),
|
||||
};
|
||||
}
|
||||
|
||||
private static (string?, string?) ApplyFormatChoice(WizardPayload p, string choice) => choice switch
|
||||
{
|
||||
"online" => (WizardStepNames.Location, SetFormat(p, WizardSessionFormat.Online)),
|
||||
"offline" => (WizardStepNames.Location, SetFormat(p, WizardSessionFormat.Offline)),
|
||||
_ => (null, "Неизвестный выбор"),
|
||||
};
|
||||
|
||||
private static (string?, string?) ApplyVisibilityChoice(WizardPayload p, string choice) => choice switch
|
||||
{
|
||||
"public" => (NextAfterVisibility(p), SetVisibility(p, WizardVisibility.Public)),
|
||||
@@ -349,7 +368,7 @@ public sealed class GameCreationWizard
|
||||
{
|
||||
"_custom" => (WizardStepNames.PoolSystemDuration, null),
|
||||
{ } c when c.Contains(':') => SplitSystemDuration(c) is (var sys, var dur)
|
||||
? (WizardStepNames.Visibility, SetSystem(p, sys) ?? SetDurationMinutes(p, dur))
|
||||
? (WizardStepNames.Format, SetSystem(p, sys) ?? SetDurationMinutes(p, dur))
|
||||
: (null, "Неверный выбор"),
|
||||
_ => (null, "Неизвестный выбор"),
|
||||
};
|
||||
@@ -391,13 +410,15 @@ public sealed class GameCreationWizard
|
||||
WizardStepNames.Duration => WizardStepNames.System,
|
||||
WizardStepNames.DateTime => WizardStepNames.Duration,
|
||||
WizardStepNames.Capacity => WizardStepNames.DateTime,
|
||||
WizardStepNames.Visibility => WizardStepNames.Capacity,
|
||||
WizardStepNames.Format => p.Type == WizardCreationType.Pool ? WizardStepNames.PoolSystemDuration : WizardStepNames.Capacity,
|
||||
WizardStepNames.Location => WizardStepNames.Format,
|
||||
WizardStepNames.Visibility => WizardStepNames.Location,
|
||||
WizardStepNames.PickClub => WizardStepNames.Visibility,
|
||||
WizardStepNames.Publish => WizardStepNames.PickClub,
|
||||
WizardStepNames.Confirm => WizardStepNames.Publish,
|
||||
|
||||
WizardStepNames.PoolSystemDuration => null, // first pool step
|
||||
WizardStepNames.PoolAddSlots => WizardStepNames.PoolSystemDuration,
|
||||
WizardStepNames.PoolAddSlots => WizardStepNames.Visibility,
|
||||
WizardStepNames.PoolSlotDateTime => WizardStepNames.PoolAddSlots,
|
||||
WizardStepNames.PoolSlotCapacity => WizardStepNames.PoolSlotDateTime,
|
||||
WizardStepNames.PoolConfirm => WizardStepNames.PoolAddSlots,
|
||||
@@ -442,6 +463,15 @@ public sealed class GameCreationWizard
|
||||
private static string? SetClubId(WizardPayload p, Guid v) { p.ClubId = v; return null; }
|
||||
private static string? SetType(WizardPayload p, WizardCreationType v) { p.Type = v; return null; }
|
||||
private static string? SetPublishInShowcase(WizardPayload p, bool v) { p.PublishInShowcase = v; return null; }
|
||||
private static string? SetFormat(WizardPayload p, WizardSessionFormat v)
|
||||
{
|
||||
p.Format = v;
|
||||
p.JoinLink = null;
|
||||
p.LocationAddress = null;
|
||||
return null;
|
||||
}
|
||||
private static string? SetJoinLink(WizardPayload p, string v) { p.JoinLink = v; p.LocationAddress = null; return null; }
|
||||
private static string? SetLocationAddress(WizardPayload p, string v) { p.LocationAddress = v; p.JoinLink = null; return null; }
|
||||
|
||||
private static string? SetCurrentSlotDateTime(WizardPayload p, DateTimeOffset v)
|
||||
{
|
||||
@@ -488,8 +518,8 @@ public sealed class GameCreationWizard
|
||||
private static string? NextAfterSystem(WizardPayload p) => WizardStepNames.Duration;
|
||||
private static string? NextAfterDuration(WizardPayload p)
|
||||
{
|
||||
if (p.Type == WizardCreationType.Pool) return WizardStepNames.Visibility;
|
||||
return p.Single?.MaxPlayers is not null ? WizardStepNames.Visibility : WizardStepNames.DateTime;
|
||||
if (p.Type == WizardCreationType.Pool) return WizardStepNames.Format;
|
||||
return p.Single?.MaxPlayers is not null ? WizardStepNames.Format : WizardStepNames.DateTime;
|
||||
}
|
||||
private static string? NextAfterVisibility(WizardPayload p)
|
||||
{
|
||||
|
||||
@@ -8,6 +8,9 @@ public enum WizardCreationType { Single, Pool }
|
||||
|
||||
public enum WizardVisibility { Public, Club, Members }
|
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<WizardSessionFormat>))]
|
||||
public enum WizardSessionFormat { Online, Offline }
|
||||
|
||||
public sealed class WizardSlotInput
|
||||
{
|
||||
public DateTimeOffset ScheduledAt { get; set; }
|
||||
@@ -30,6 +33,9 @@ public sealed class WizardPayload
|
||||
public string? ImageUrl { get; set; }
|
||||
public string? System { get; set; }
|
||||
public int? DurationMinutes { get; set; }
|
||||
public WizardSessionFormat? Format { get; set; }
|
||||
public string? JoinLink { get; set; }
|
||||
public string? LocationAddress { get; set; }
|
||||
public WizardVisibility? Visibility { get; set; }
|
||||
public Guid? ClubId { get; set; }
|
||||
public bool? PublishInShowcase { get; set; }
|
||||
|
||||
@@ -14,4 +14,5 @@ public static class WizardStepLimits
|
||||
public const int MinCapacity = 1;
|
||||
public const int MinDurationHours = 1;
|
||||
public const int MaxDurationHours = 12;
|
||||
public const int MaxLocationLength = 500;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ public static class WizardStepNames
|
||||
public const string Duration = "Duration";
|
||||
public const string DateTime = "DateTime";
|
||||
public const string Capacity = "Capacity";
|
||||
public const string Format = "Format";
|
||||
public const string Location = "Location";
|
||||
public const string Visibility = "Visibility";
|
||||
public const string PickClub = "PickClub";
|
||||
public const string Publish = "Publish";
|
||||
|
||||
@@ -30,6 +30,8 @@ public static class WizardStepViewBuilder
|
||||
WizardStepNames.Duration => BuildDuration(),
|
||||
WizardStepNames.DateTime => BuildDateTime(),
|
||||
WizardStepNames.Capacity => BuildCapacity(),
|
||||
WizardStepNames.Format => BuildFormat(),
|
||||
WizardStepNames.Location => BuildLocation(payload),
|
||||
WizardStepNames.Visibility => BuildVisibility(),
|
||||
WizardStepNames.PickClub => BuildPickClub(clubs ?? Array.Empty<WizardClubOption>()),
|
||||
WizardStepNames.Publish => BuildPublish(),
|
||||
@@ -105,6 +107,22 @@ public static class WizardStepViewBuilder
|
||||
new("♾ Без лимита", WizardCallbackData.Choice(WizardStepNames.Capacity, "no_limit"), WizardActionStyle.Primary),
|
||||
});
|
||||
|
||||
private static (string, IReadOnlyList<WizardAction>) BuildFormat() => (
|
||||
"🧭 Выберите формат игры.",
|
||||
new List<WizardAction>
|
||||
{
|
||||
new("🌐 Online", WizardCallbackData.Choice(WizardStepNames.Format, "online"), WizardActionStyle.Primary),
|
||||
new("📍 Offline", WizardCallbackData.Choice(WizardStepNames.Format, "offline"), WizardActionStyle.Primary),
|
||||
new("⬅️ Назад", WizardCallbackData.Back()),
|
||||
new("❌ Отмена", WizardCallbackData.Cancel(), WizardActionStyle.Danger),
|
||||
});
|
||||
|
||||
private static (string, IReadOnlyList<WizardAction>) BuildLocation(WizardPayload payload) => payload.Format switch
|
||||
{
|
||||
WizardSessionFormat.Offline => ("📍 Введите адрес места проведения.", BackCancel()),
|
||||
_ => ("🔗 Введите ссылку для подключения к online-игре.", BackCancel()),
|
||||
};
|
||||
|
||||
private static (string, IReadOnlyList<WizardAction>) BuildVisibility() => (
|
||||
"🔒 Выберите видимость.",
|
||||
new List<WizardAction>
|
||||
@@ -150,6 +168,7 @@ public static class WizardStepViewBuilder
|
||||
if (!string.IsNullOrEmpty(p.Description)) sb.AppendLine($"📄 {p.Description}");
|
||||
if (!string.IsNullOrEmpty(p.System)) sb.AppendLine($"🎲 Система: {p.System}");
|
||||
if (p.DurationMinutes.HasValue) sb.AppendLine($"⏱ Длительность: {p.DurationMinutes / 60} ч");
|
||||
AppendFormatLocation(sb, p);
|
||||
if (p.Single?.ScheduledAt is { } at) sb.AppendLine($"📅 {at.FormatMoscow()} (МСК)");
|
||||
if (p.Single?.MaxPlayers is { } mp) sb.AppendLine($"👥 Мест: {mp}, waitlist {(p.Waitlist == true ? "вкл" : "выкл")}");
|
||||
sb.AppendLine($"🔒 Видимость: {RenderVisibilityText(p.Visibility)}");
|
||||
@@ -204,6 +223,7 @@ public static class WizardStepViewBuilder
|
||||
if (!string.IsNullOrEmpty(p.Description)) sb.AppendLine($"📄 {p.Description}");
|
||||
if (!string.IsNullOrEmpty(p.System)) sb.AppendLine($"🎲 Система: {p.System}");
|
||||
if (p.DurationMinutes.HasValue) sb.AppendLine($"⏱ Длительность: {p.DurationMinutes / 60} ч");
|
||||
AppendFormatLocation(sb, p);
|
||||
sb.AppendLine($"🔒 Видимость: {RenderVisibilityText(p.Visibility)}");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"Слоты ({p.Pool?.Slots.Count ?? 0}):");
|
||||
@@ -245,4 +265,19 @@ public static class WizardStepViewBuilder
|
||||
WizardVisibility.Members => "только для членов клуба",
|
||||
_ => "не задана",
|
||||
};
|
||||
|
||||
private static void AppendFormatLocation(StringBuilder sb, WizardPayload p)
|
||||
{
|
||||
if (p.Format is null) return;
|
||||
|
||||
sb.AppendLine($"🧭 Формат: {p.Format}");
|
||||
if (p.Format == WizardSessionFormat.Online && !string.IsNullOrWhiteSpace(p.JoinLink))
|
||||
{
|
||||
sb.AppendLine($"🔗 Ссылка: {p.JoinLink}");
|
||||
}
|
||||
else if (p.Format == WizardSessionFormat.Offline && !string.IsNullOrWhiteSpace(p.LocationAddress))
|
||||
{
|
||||
sb.AppendLine($"📍 Адрес: {p.LocationAddress}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -159,7 +159,7 @@ public sealed class HandleRescheduleTimeInputHandler(
|
||||
await transaction.CommitAsync(ct);
|
||||
|
||||
var batchSessions = (await connection.QueryAsync<SessionBatchDto>(
|
||||
"SELECT id AS SessionId, scheduled_at AS ScheduledAt, status AS Status, max_players AS MaxPlayers, join_link AS JoinLink FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at",
|
||||
"SELECT id AS SessionId, scheduled_at AS ScheduledAt, status AS Status, max_players AS MaxPlayers, join_link AS JoinLink, format AS Format, location_address AS LocationAddress FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at",
|
||||
new { proposal.BatchId })).ToList();
|
||||
|
||||
var batchParticipants = (await connection.QueryAsync<ParticipantBatchDto>(
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
namespace GmRelay.Shared.Rendering;
|
||||
|
||||
public sealed record SessionBatchDto(Guid SessionId, DateTime ScheduledAt, string Status, int? MaxPlayers, string JoinLink);
|
||||
public sealed record SessionBatchDto(
|
||||
Guid SessionId,
|
||||
DateTime ScheduledAt,
|
||||
string Status,
|
||||
int? MaxPlayers,
|
||||
string JoinLink,
|
||||
string? Format = null,
|
||||
string? LocationAddress = null);
|
||||
public sealed record ParticipantBatchDto(Guid SessionId, string DisplayName, string? TelegramUsername, string RegistrationStatus);
|
||||
|
||||
@@ -39,6 +39,8 @@ public static class SessionBatchViewBuilder
|
||||
session.Status,
|
||||
session.MaxPlayers,
|
||||
session.JoinLink,
|
||||
session.Format,
|
||||
session.LocationAddress,
|
||||
activePlayers.Count,
|
||||
activePlayers,
|
||||
waitlistedPlayers,
|
||||
|
||||
@@ -12,6 +12,8 @@ public sealed record SessionViewItem(
|
||||
string Status,
|
||||
int? MaxPlayers,
|
||||
string JoinLink,
|
||||
string? Format,
|
||||
string? LocationAddress,
|
||||
int ActivePlayerCount,
|
||||
IReadOnlyList<PlayerViewItem> ActivePlayers,
|
||||
IReadOnlyList<PlayerViewItem> WaitlistedPlayers,
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="nav-version">v3.9.9</div>
|
||||
<div class="nav-version">v3.10.0</div>
|
||||
</div>
|
||||
</Authorized>
|
||||
<NotAuthorized>
|
||||
|
||||
@@ -1897,7 +1897,7 @@ public sealed class SessionService(
|
||||
await using var conn = await dataSource.OpenConnectionAsync();
|
||||
|
||||
var sessions = (await conn.QueryAsync<SessionBatchDto>(
|
||||
"SELECT id AS SessionId, scheduled_at AS ScheduledAt, status AS Status, max_players AS MaxPlayers, join_link AS JoinLink FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at",
|
||||
"SELECT id AS SessionId, scheduled_at AS ScheduledAt, status AS Status, max_players AS MaxPlayers, join_link AS JoinLink, format AS Format, location_address AS LocationAddress FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at",
|
||||
new { BatchId = batchId })).ToList();
|
||||
|
||||
var participants = (await conn.QueryAsync<ParticipantBatchDto>(
|
||||
|
||||
@@ -22,7 +22,14 @@ public static class TelegramSessionBatchRenderer
|
||||
|
||||
if (!string.IsNullOrEmpty(session.JoinLink))
|
||||
{
|
||||
messageText += $"🔗 <a href=\"{System.Net.WebUtility.HtmlEncode(session.JoinLink)}\">Ссылка на игру</a>\n";
|
||||
var encodedLink = System.Net.WebUtility.HtmlEncode(session.JoinLink);
|
||||
messageText += $"🔗 Ссылка на игру: <a href=\"{encodedLink}\">{encodedLink}</a>\n";
|
||||
}
|
||||
|
||||
if (string.Equals(session.Format, "Offline", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.IsNullOrWhiteSpace(session.LocationAddress))
|
||||
{
|
||||
messageText += $"📍 Адрес: {System.Net.WebUtility.HtmlEncode(session.LocationAddress)}\n";
|
||||
}
|
||||
|
||||
if (session.ActivePlayers.Count > 0)
|
||||
|
||||
Reference in New Issue
Block a user