Add Telegram bot integration and related features
ci / build-backend (push) Successful in 2m38s
ci / build-frontend (push) Successful in 1m16s
ci / tests (push) Successful in 2m33s
ci / sonar (push) Successful in 3m48s

Implemented Telegram bot functionality, including settings management, subscriber tracking, and link generation for user interaction. Updated the backend to support new Telegram-related services and database entities. Enhanced the frontend to display Telegram options and allow users to open a chat with the bot. Localization strings were added for both English and Russian to support the new features. This integration aims to improve user engagement through Telegram notifications and interactions.
This commit is contained in:
Leonid Pershin
2026-07-31 07:58:18 +03:00
parent bd3ced3637
commit 2b03e43a83
53 changed files with 6566 additions and 2 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,201 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace TeleWave.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class TelegramBot : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "TelegramLinkCodes",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Code = table.Column<string>(
type: "character varying(64)",
maxLength: 64,
nullable: false
),
UserId = table.Column<Guid>(type: "uuid", nullable: false),
CreatedAt = table.Column<DateTimeOffset>(
type: "timestamp with time zone",
nullable: false
),
ExpiresAt = table.Column<DateTimeOffset>(
type: "timestamp with time zone",
nullable: false
),
UsedAt = table.Column<DateTimeOffset>(
type: "timestamp with time zone",
nullable: true
),
},
constraints: table =>
{
table.PrimaryKey("PK_TelegramLinkCodes", x => x.Id);
}
);
migrationBuilder.CreateTable(
name: "TelegramSettings",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
IsEnabled = table.Column<bool>(type: "boolean", nullable: false),
BotToken = table.Column<string>(
type: "character varying(128)",
maxLength: 128,
nullable: true
),
BotUsername = table.Column<string>(
type: "character varying(64)",
maxLength: 64,
nullable: true
),
Transport = table.Column<int>(type: "integer", nullable: false),
WebhookUrl = table.Column<string>(
type: "character varying(512)",
maxLength: 512,
nullable: true
),
WebhookSecret = table.Column<string>(
type: "character varying(128)",
maxLength: 128,
nullable: true
),
ProxyKind = table.Column<int>(type: "integer", nullable: false),
ProxyHost = table.Column<string>(
type: "character varying(256)",
maxLength: 256,
nullable: true
),
ProxyPort = table.Column<int>(type: "integer", nullable: true),
ProxyUsername = table.Column<string>(
type: "character varying(128)",
maxLength: 128,
nullable: true
),
ProxyPassword = table.Column<string>(
type: "character varying(256)",
maxLength: 256,
nullable: true
),
LastUpdateId = table.Column<long>(type: "bigint", nullable: false),
LastContactAt = table.Column<DateTimeOffset>(
type: "timestamp with time zone",
nullable: true
),
LastError = table.Column<string>(
type: "character varying(512)",
maxLength: 512,
nullable: true
),
},
constraints: table =>
{
table.PrimaryKey("PK_TelegramSettings", x => x.Id);
}
);
migrationBuilder.CreateTable(
name: "TelegramSubscribers",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
ChatId = table.Column<long>(type: "bigint", nullable: false),
UserId = table.Column<Guid>(type: "uuid", nullable: false),
DisplayName = table.Column<string>(
type: "character varying(128)",
maxLength: 128,
nullable: true
),
CreatedAt = table.Column<DateTimeOffset>(
type: "timestamp with time zone",
nullable: false
),
LastSeenAt = table.Column<DateTimeOffset>(
type: "timestamp with time zone",
nullable: true
),
IsStopped = table.Column<bool>(type: "boolean", nullable: false),
},
constraints: table =>
{
table.PrimaryKey("PK_TelegramSubscribers", x => x.Id);
}
);
migrationBuilder.CreateTable(
name: "TelegramSubscriptions",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
SubscriberId = table.Column<Guid>(type: "uuid", nullable: false),
ChannelId = table.Column<Guid>(type: "uuid", nullable: false),
Kind = table.Column<int>(type: "integer", nullable: false),
},
constraints: table =>
{
table.PrimaryKey("PK_TelegramSubscriptions", x => x.Id);
table.ForeignKey(
name: "FK_TelegramSubscriptions_TelegramSubscribers_SubscriberId",
column: x => x.SubscriberId,
principalTable: "TelegramSubscribers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade
);
}
);
migrationBuilder.CreateIndex(
name: "IX_TelegramLinkCodes_Code",
table: "TelegramLinkCodes",
column: "Code",
unique: true
);
migrationBuilder.CreateIndex(
name: "IX_TelegramSubscribers_ChatId",
table: "TelegramSubscribers",
column: "ChatId",
unique: true
);
migrationBuilder.CreateIndex(
name: "IX_TelegramSubscribers_UserId",
table: "TelegramSubscribers",
column: "UserId"
);
migrationBuilder.CreateIndex(
name: "IX_TelegramSubscriptions_ChannelId_Kind",
table: "TelegramSubscriptions",
columns: new[] { "ChannelId", "Kind" }
);
migrationBuilder.CreateIndex(
name: "IX_TelegramSubscriptions_SubscriberId_ChannelId_Kind",
table: "TelegramSubscriptions",
columns: new[] { "SubscriberId", "ChannelId", "Kind" },
unique: true
);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(name: "TelegramLinkCodes");
migrationBuilder.DropTable(name: "TelegramSettings");
migrationBuilder.DropTable(name: "TelegramSubscriptions");
migrationBuilder.DropTable(name: "TelegramSubscribers");
}
}
}
@@ -0,0 +1,28 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace TeleWave.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class TelegramNotifiedMarker : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTimeOffset>(
name: "NotifiedUntil",
table: "TelegramSettings",
type: "timestamp with time zone",
nullable: true
);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(name: "NotifiedUntil", table: "TelegramSettings");
}
}
}
@@ -736,6 +736,157 @@ namespace TeleWave.Infrastructure.Migrations
b.ToTable("MediaAssets");
});
modelBuilder.Entity("TeleWave.Domain.Notifications.TelegramLinkCode", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTimeOffset>("ExpiresAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTimeOffset?>("UsedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("Code")
.IsUnique();
b.ToTable("TelegramLinkCodes");
});
modelBuilder.Entity("TeleWave.Domain.Notifications.TelegramSettings", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<string>("BotToken")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("BotUsername")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<bool>("IsEnabled")
.HasColumnType("boolean");
b.Property<DateTimeOffset?>("LastContactAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("LastError")
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<long>("LastUpdateId")
.HasColumnType("bigint");
b.Property<DateTimeOffset?>("NotifiedUntil")
.HasColumnType("timestamp with time zone");
b.Property<string>("ProxyHost")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<int>("ProxyKind")
.HasColumnType("integer");
b.Property<string>("ProxyPassword")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<int?>("ProxyPort")
.HasColumnType("integer");
b.Property<string>("ProxyUsername")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<int>("Transport")
.HasColumnType("integer");
b.Property<string>("WebhookSecret")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("WebhookUrl")
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.HasKey("Id");
b.ToTable("TelegramSettings");
});
modelBuilder.Entity("TeleWave.Domain.Notifications.TelegramSubscriber", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<long>("ChatId")
.HasColumnType("bigint");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("DisplayName")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<bool>("IsStopped")
.HasColumnType("boolean");
b.Property<DateTimeOffset?>("LastSeenAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("ChatId")
.IsUnique();
b.HasIndex("UserId");
b.ToTable("TelegramSubscribers");
});
modelBuilder.Entity("TeleWave.Domain.Notifications.TelegramSubscription", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<Guid>("ChannelId")
.HasColumnType("uuid");
b.Property<int>("Kind")
.HasColumnType("integer");
b.Property<Guid>("SubscriberId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("ChannelId", "Kind");
b.HasIndex("SubscriberId", "ChannelId", "Kind")
.IsUnique();
b.ToTable("TelegramSubscriptions");
});
modelBuilder.Entity("TeleWave.Domain.Programming.GridLayer", b =>
{
b.Property<Guid>("Id")
@@ -1313,6 +1464,15 @@ namespace TeleWave.Infrastructure.Migrations
.IsRequired();
});
modelBuilder.Entity("TeleWave.Domain.Notifications.TelegramSubscription", b =>
{
b.HasOne("TeleWave.Domain.Notifications.TelegramSubscriber", null)
.WithMany("Subscriptions")
.HasForeignKey("SubscriberId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("TeleWave.Domain.Programming.GridLayer", b =>
{
b.HasOne("TeleWave.Domain.Programming.ScheduleTemplate", null)
@@ -1400,6 +1560,11 @@ namespace TeleWave.Infrastructure.Migrations
b.Navigation("Genres");
});
modelBuilder.Entity("TeleWave.Domain.Notifications.TelegramSubscriber", b =>
{
b.Navigation("Subscriptions");
});
modelBuilder.Entity("TeleWave.Domain.Programming.GridLayer", b =>
{
b.Navigation("Slots");