Add image management functionality: introduce Image entity and related API endpoints, update database schema to support image storage, and enhance UI with a new gallery feature for image selection and upload. Update translations for gallery-related terms.

This commit is contained in:
Leonid Pershin
2026-07-25 11:27:34 +03:00
parent 72451f89a8
commit 149cd153b9
34 changed files with 1732 additions and 11 deletions
@@ -136,6 +136,7 @@ public static class DependencyInjection
services.AddSingleton<MediaPathResolver>();
services.AddSingleton<IMediaStorage, FileSystemMediaStorage>();
services.AddSingleton<IBumperTemplateStorage, BumperTemplateStorage>();
services.AddSingleton<IImageStore, ImageStore>();
services.AddSingleton<IAudioProbe, FfprobeAudioProbe>();
services.AddSingleton<IMediaProcessor, FfmpegMediaProcessor>();
services.AddSingleton<IMediaProcessingQueue, MediaProcessingQueue>();
@@ -0,0 +1,48 @@
using TeleWave.Application.Common.Interfaces;
namespace TeleWave.Infrastructure.Media;
/// <summary>Файловое хранилище общего реестра изображений: images/{imageId}{ext}.</summary>
public sealed class ImageStore(MediaPathResolver paths) : IImageStore
{
public async Task SaveAsync(
Guid imageId,
string extension,
Stream content,
CancellationToken cancellationToken
)
{
Directory.CreateDirectory(paths.ImagesDir);
var path = paths.ImagePath(imageId, Normalize(extension));
await using var fs = File.Create(path);
await content.CopyToAsync(fs, cancellationToken);
}
public async Task SaveAsync(
Guid imageId,
string extension,
byte[] content,
CancellationToken cancellationToken
)
{
Directory.CreateDirectory(paths.ImagesDir);
var path = paths.ImagePath(imageId, Normalize(extension));
await File.WriteAllBytesAsync(path, content, cancellationToken);
}
public void Delete(Guid imageId, string extension)
{
var path = paths.ImagePath(imageId, Normalize(extension));
if (File.Exists(path))
File.Delete(path);
}
public string? ResolvePath(Guid imageId, string extension)
{
var path = paths.ImagePath(imageId, Normalize(extension));
return File.Exists(path) ? path : null;
}
private static string Normalize(string extension) =>
extension.StartsWith('.') ? extension : "." + extension;
}
@@ -19,6 +19,7 @@ public sealed class MediaPathResolver
AssetsDir = Path.Combine(_root, "assets");
BumpersDir = Path.Combine(_root, "bumpers");
MetadataDir = Path.Combine(_root, "metadata");
ImagesDir = Path.Combine(_root, "images");
}
public string InboxDir { get; }
@@ -32,6 +33,9 @@ public sealed class MediaPathResolver
/// <summary>Картинки метаданных (постеры/кадры), скачанные локально.</summary>
public string MetadataDir { get; }
/// <summary>Общий реестр изображений (галерея): файлы images/{imageId}{ext}.</summary>
public string ImagesDir { get; }
public void EnsureDirectories()
{
Directory.CreateDirectory(InboxDir);
@@ -40,6 +44,7 @@ public sealed class MediaPathResolver
Directory.CreateDirectory(AssetsDir);
Directory.CreateDirectory(BumpersDir);
Directory.CreateDirectory(MetadataDir);
Directory.CreateDirectory(ImagesDir);
}
public string MetadataShowDir(Guid showId) =>
@@ -86,6 +91,10 @@ public sealed class MediaPathResolver
public string BumperTemplateFilePath(Guid templateId, string kind, string extension) =>
EnsureWithinRoot(Path.Combine(BumpersDir, templateId.ToString("N"), kind + extension));
/// <summary>Путь к файлу изображения общего реестра (extension — с точкой).</summary>
public string ImagePath(Guid imageId, string extension) =>
EnsureWithinRoot(Path.Combine(ImagesDir, imageId.ToString("N") + extension));
public string OriginalPath(Guid assetId, string extension) =>
EnsureWithinRoot(Path.Combine(OriginalsDir, assetId.ToString("N") + extension));
@@ -0,0 +1,889 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using TeleWave.Infrastructure.Persistence;
#nullable disable
namespace TeleWave.Infrastructure.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20260725082032_ImageRegistry")]
partial class ImageRegistry
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.10")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<Guid>("RoleId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("ProviderKey")
.HasColumnType("text");
b.Property<string>("ProviderDisplayName")
.HasColumnType("text");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.Property<Guid>("RoleId")
.HasColumnType("uuid");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<string>("Value")
.HasColumnType("text");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("TeleWave.Domain.Auth.RefreshToken", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTimeOffset>("ExpiresAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("ReplacedByTokenHash")
.HasColumnType("text");
b.Property<DateTimeOffset?>("RevokedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("TokenHash")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TokenHash")
.IsUnique();
b.HasIndex("UserId");
b.ToTable("RefreshTokens");
});
modelBuilder.Entity("TeleWave.Domain.Broadcast.BumperAsset", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("FromShowId")
.HasColumnType("uuid");
b.Property<Guid>("MediaAssetId")
.HasColumnType("uuid");
b.Property<string>("Signature")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<Guid>("ToShowId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("FromShowId", "ToShowId", "Signature");
b.ToTable("BumperAssets");
});
modelBuilder.Entity("TeleWave.Domain.Broadcast.BumperTemplate", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<string>("AccentColor")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<double?>("AudioDurationSeconds")
.HasColumnType("double precision");
b.Property<string>("AudioExtension")
.HasMaxLength(16)
.HasColumnType("character varying(16)");
b.Property<string>("BackgroundColor")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<string>("BackgroundColor2")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<string>("BackgroundImageExtension")
.HasMaxLength(16)
.HasColumnType("character varying(16)");
b.Property<Guid>("ChannelId")
.HasColumnType("uuid");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<int>("Position")
.HasColumnType("integer");
b.Property<int>("Revision")
.HasColumnType("integer");
b.Property<string>("TextColor")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.HasKey("Id");
b.HasIndex("ChannelId", "Position");
b.ToTable("BumperTemplate");
});
modelBuilder.Entity("TeleWave.Domain.Broadcast.Channel", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<int>("AdInsertion")
.HasColumnType("integer");
b.Property<int>("AdsPerBreak")
.HasColumnType("integer");
b.Property<int>("BumperFont")
.HasColumnType("integer");
b.Property<int>("BumperMinIntervalMinutes")
.HasColumnType("integer");
b.Property<string>("BumperNextLabel")
.IsRequired()
.HasColumnType("text");
b.Property<string>("BumperNowLabel")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("BumperOnlyBetweenDifferentShows")
.HasColumnType("boolean");
b.Property<int>("BumperSelection")
.HasColumnType("integer");
b.Property<bool>("BumpersEnabled")
.HasColumnType("boolean");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTimeOffset>("EpochUtc")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("FillerAssetId")
.HasColumnType("uuid");
b.Property<bool>("IsEnabled")
.HasColumnType("boolean");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<int>("NextAdIndex")
.HasColumnType("integer");
b.Property<int>("NextBumperIndex")
.HasColumnType("integer");
b.Property<string>("Slug")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.HasKey("Id");
b.HasIndex("Slug")
.IsUnique();
b.ToTable("Channels");
});
modelBuilder.Entity("TeleWave.Domain.Broadcast.ChannelAd", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<Guid>("ChannelId")
.HasColumnType("uuid");
b.Property<Guid>("MediaAssetId")
.HasColumnType("uuid");
b.Property<int>("Position")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("ChannelId", "Position");
b.ToTable("ChannelAd");
});
modelBuilder.Entity("TeleWave.Domain.Broadcast.ChannelShow", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<int>("BlockMode")
.HasColumnType("integer");
b.Property<int>("BlockValue")
.HasColumnType("integer");
b.Property<Guid>("ChannelId")
.HasColumnType("uuid");
b.Property<bool>("IsEnabled")
.HasColumnType("boolean");
b.Property<int>("NextEpisodeIndex")
.HasColumnType("integer");
b.Property<Guid>("ShowId")
.HasColumnType("uuid");
b.Property<int>("Weight")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("ChannelId", "ShowId");
b.ToTable("ChannelShow");
});
modelBuilder.Entity("TeleWave.Domain.Broadcast.OverrideShow", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<Guid>("ProgrammingOverrideId")
.HasColumnType("uuid");
b.Property<Guid>("ShowId")
.HasColumnType("uuid");
b.Property<int>("Weight")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("ProgrammingOverrideId");
b.ToTable("OverrideShow");
});
modelBuilder.Entity("TeleWave.Domain.Broadcast.ProgrammingOverride", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<Guid>("ChannelId")
.HasColumnType("uuid");
b.Property<DateTimeOffset>("EndsAtUtc")
.HasColumnType("timestamp with time zone");
b.Property<int>("Mode")
.HasColumnType("integer");
b.Property<DateTimeOffset>("StartsAtUtc")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("ChannelId", "StartsAtUtc", "EndsAtUtc");
b.ToTable("ProgrammingOverride");
});
modelBuilder.Entity("TeleWave.Domain.Broadcast.ScheduleEntry", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<Guid>("ChannelId")
.HasColumnType("uuid");
b.Property<DateTimeOffset>("EndsAtUtc")
.HasColumnType("timestamp with time zone");
b.Property<int?>("EpisodeIndex")
.HasColumnType("integer");
b.Property<int>("Kind")
.HasColumnType("integer");
b.Property<Guid>("MediaAssetId")
.HasColumnType("uuid");
b.Property<Guid?>("ShowId")
.HasColumnType("uuid");
b.Property<DateTimeOffset>("StartsAtUtc")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("ChannelId", "EndsAtUtc");
b.HasIndex("ChannelId", "ShowId");
b.HasIndex("ChannelId", "StartsAtUtc");
b.ToTable("ScheduleEntries");
});
modelBuilder.Entity("TeleWave.Domain.Images.Image", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<int>("Category")
.HasColumnType("integer");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("FileExtension")
.IsRequired()
.HasMaxLength(16)
.HasColumnType("character varying(16)");
b.Property<string>("OriginalFileName")
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.HasKey("Id");
b.HasIndex("Category", "CreatedAt");
b.ToTable("Images");
});
modelBuilder.Entity("TeleWave.Domain.Library.Show", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.HasMaxLength(2048)
.HasColumnType("character varying(2048)");
b.Property<int>("Kind")
.HasColumnType("integer");
b.Property<string>("MetadataExternalId")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("MetadataProvider")
.HasMaxLength(16)
.HasColumnType("character varying(16)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("OriginalName")
.HasColumnType("text");
b.Property<string>("PosterPath")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<int?>("Year")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("Shows");
});
modelBuilder.Entity("TeleWave.Domain.Library.ShowEpisode", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<DateOnly?>("AirDate")
.HasColumnType("date");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int?>("Episode")
.HasColumnType("integer");
b.Property<Guid>("MediaAssetId")
.HasColumnType("uuid");
b.Property<string>("Overview")
.HasMaxLength(4096)
.HasColumnType("character varying(4096)");
b.Property<int>("Position")
.HasColumnType("integer");
b.Property<int?>("Season")
.HasColumnType("integer");
b.Property<Guid>("ShowId")
.HasColumnType("uuid");
b.Property<string>("StillPath")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("Title")
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.HasKey("Id");
b.HasIndex("MediaAssetId");
b.HasIndex("ShowId", "Position");
b.ToTable("ShowEpisode");
});
modelBuilder.Entity("TeleWave.Domain.Media.MediaAsset", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<string>("AudioCodec")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<TimeSpan?>("Duration")
.HasColumnType("interval");
b.Property<string>("ErrorMessage")
.HasMaxLength(2048)
.HasColumnType("character varying(2048)");
b.Property<int?>("Height")
.HasColumnType("integer");
b.Property<string>("OriginalExtension")
.IsRequired()
.HasMaxLength(16)
.HasColumnType("character varying(16)");
b.Property<string>("OriginalFileName")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<string>("RelativePath")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<int?>("SegmentCount")
.HasColumnType("integer");
b.Property<int?>("SegmentSeconds")
.HasColumnType("integer");
b.Property<int>("Source")
.HasColumnType("integer");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<DateTimeOffset>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("VideoCodec")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<int?>("Width")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("CreatedAt");
b.HasIndex("Status");
b.ToTable("MediaAssets");
});
modelBuilder.Entity("TeleWave.Domain.Settings.AppSetting", b =>
{
b.Property<string>("Key")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(1024)
.HasColumnType("character varying(1024)");
b.HasKey("Key");
b.ToTable("AppSettings");
});
modelBuilder.Entity("TeleWave.Infrastructure.Identity.AppRole", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<bool>("IsSystem")
.HasColumnType("boolean");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("TeleWave.Infrastructure.Identity.AppUser", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<int>("AccessFailedCount")
.HasColumnType("integer");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("boolean");
b.Property<bool>("IsBlocked")
.HasColumnType("boolean");
b.Property<bool>("LockoutEnabled")
.HasColumnType("boolean");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("timestamp with time zone");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("PasswordHash")
.HasColumnType("text");
b.Property<string>("PhoneNumber")
.HasColumnType("text");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("boolean");
b.Property<string>("SecurityStamp")
.HasColumnType("text");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("boolean");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
{
b.HasOne("TeleWave.Infrastructure.Identity.AppRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.HasOne("TeleWave.Infrastructure.Identity.AppUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.HasOne("TeleWave.Infrastructure.Identity.AppUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
{
b.HasOne("TeleWave.Infrastructure.Identity.AppRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("TeleWave.Infrastructure.Identity.AppUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.HasOne("TeleWave.Infrastructure.Identity.AppUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("TeleWave.Domain.Broadcast.BumperTemplate", b =>
{
b.HasOne("TeleWave.Domain.Broadcast.Channel", null)
.WithMany("BumperTemplates")
.HasForeignKey("ChannelId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("TeleWave.Domain.Broadcast.ChannelAd", b =>
{
b.HasOne("TeleWave.Domain.Broadcast.Channel", null)
.WithMany("Ads")
.HasForeignKey("ChannelId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("TeleWave.Domain.Broadcast.ChannelShow", b =>
{
b.HasOne("TeleWave.Domain.Broadcast.Channel", null)
.WithMany("Shows")
.HasForeignKey("ChannelId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("TeleWave.Domain.Broadcast.OverrideShow", b =>
{
b.HasOne("TeleWave.Domain.Broadcast.ProgrammingOverride", null)
.WithMany("Shows")
.HasForeignKey("ProgrammingOverrideId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("TeleWave.Domain.Broadcast.ProgrammingOverride", b =>
{
b.HasOne("TeleWave.Domain.Broadcast.Channel", null)
.WithMany("Overrides")
.HasForeignKey("ChannelId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("TeleWave.Domain.Library.ShowEpisode", b =>
{
b.HasOne("TeleWave.Domain.Library.Show", null)
.WithMany("Episodes")
.HasForeignKey("ShowId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("TeleWave.Domain.Broadcast.Channel", b =>
{
b.Navigation("Ads");
b.Navigation("BumperTemplates");
b.Navigation("Overrides");
b.Navigation("Shows");
});
modelBuilder.Entity("TeleWave.Domain.Broadcast.ProgrammingOverride", b =>
{
b.Navigation("Shows");
});
modelBuilder.Entity("TeleWave.Domain.Library.Show", b =>
{
b.Navigation("Episodes");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,42 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace TeleWave.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class ImageRegistry : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Images",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Category = table.Column<int>(type: "integer", nullable: false),
FileExtension = table.Column<string>(type: "character varying(16)", maxLength: 16, nullable: false),
OriginalFileName = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Images", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_Images_Category_CreatedAt",
table: "Images",
columns: new[] { "Category", "CreatedAt" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Images");
}
}
}
@@ -454,6 +454,33 @@ namespace TeleWave.Infrastructure.Migrations
b.ToTable("ScheduleEntries");
});
modelBuilder.Entity("TeleWave.Domain.Images.Image", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<int>("Category")
.HasColumnType("integer");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("FileExtension")
.IsRequired()
.HasMaxLength(16)
.HasColumnType("character varying(16)");
b.Property<string>("OriginalFileName")
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.HasKey("Id");
b.HasIndex("Category", "CreatedAt");
b.ToTable("Images");
});
modelBuilder.Entity("TeleWave.Domain.Library.Show", b =>
{
b.Property<Guid>("Id")
@@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore.Metadata;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Domain.Auth;
using TeleWave.Domain.Broadcast;
using TeleWave.Domain.Images;
using TeleWave.Domain.Library;
using TeleWave.Domain.Media;
using TeleWave.Domain.Settings;
@@ -26,6 +27,7 @@ public class AppDbContext(DbContextOptions<AppDbContext> options)
public DbSet<ScheduleEntry> ScheduleEntries => Set<ScheduleEntry>();
public DbSet<BumperAsset> BumperAssets => Set<BumperAsset>();
public DbSet<AppSetting> AppSettings => Set<AppSetting>();
public DbSet<Image> Images => Set<Image>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -0,0 +1,15 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using TeleWave.Domain.Images;
namespace TeleWave.Infrastructure.Persistence.Configurations;
public class ImageConfiguration : IEntityTypeConfiguration<Image>
{
public void Configure(EntityTypeBuilder<Image> builder)
{
builder.Property(x => x.FileExtension).IsRequired().HasMaxLength(16);
builder.Property(x => x.OriginalFileName).HasMaxLength(512);
builder.HasIndex(x => new { x.Category, x.CreatedAt });
}
}