Initial commit: GM-Relay Telegram Bot

This commit is contained in:
2026-04-13 13:52:49 +03:00
commit 9db4bee2f6
51 changed files with 3407 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
namespace GmRelay.Bot.Domain;
/// <summary>
/// Hardcoded Moscow timezone (UTC+3) for display purposes.
/// All DB storage is UTC (timestamptz). Conversion happens only at display layer.
/// Npgsql returns timestamptz as DateTime (Kind=Utc) under AOT, so we provide DateTime overloads.
/// </summary>
public static class MoscowTime
{
private static readonly TimeSpan MoscowOffset = TimeSpan.FromHours(3);
public static DateTimeOffset Now => DateTimeOffset.UtcNow.ToOffset(MoscowOffset);
public static DateTimeOffset ToMoscow(this DateTimeOffset utc) => utc.ToOffset(MoscowOffset);
public static string FormatMoscow(this DateTimeOffset utc)
=> utc.ToOffset(MoscowOffset).ToString("d MMMM yyyy, HH:mm", System.Globalization.CultureInfo.GetCultureInfo("ru-RU"));
// ── DateTime overloads (for Dapper AOT compatibility) ────────────
public static DateTime ToMoscow(this DateTime utcDt) => utcDt.Add(MoscowOffset);
public static string FormatMoscow(this DateTime utcDt)
=> utcDt.Add(MoscowOffset).ToString("d MMMM yyyy, HH:mm", System.Globalization.CultureInfo.GetCultureInfo("ru-RU"));
public static string FormatMoscowShort(this DateTime utcDt)
=> utcDt.Add(MoscowOffset).ToString("dd.MM");
public static bool TryParseMoscow(string text, out DateTimeOffset utcTime)
{
if (DateTime.TryParseExact(text, new[] { "dd.MM.yyyy HH:mm", "dd.MM.yyyy H:mm", "d.MM.yyyy HH:mm" },
System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out var localDt))
{
// Treat the parsed local time as UTC+3
utcTime = new DateTimeOffset(localDt, MoscowOffset).ToUniversalTime();
return true;
}
utcTime = default;
return false;
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace GmRelay.Bot.Domain;
public static class RsvpStatus
{
public const string Pending = "Pending";
public const string Confirmed = "Confirmed";
public const string Declined = "Declined";
}
+9
View File
@@ -0,0 +1,9 @@
namespace GmRelay.Bot.Domain;
public static class SessionStatus
{
public const string Planned = "Planned";
public const string ConfirmationSent = "ConfirmationSent";
public const string Confirmed = "Confirmed";
public const string Cancelled = "Cancelled";
}