diff --git a/src/DRS9.Dashboard.Application/DTOs/ApplicationDto.cs b/src/DRS9.Dashboard.Application/DTOs/ApplicationDto.cs index 442b4c9..face2e6 100644 --- a/src/DRS9.Dashboard.Application/DTOs/ApplicationDto.cs +++ b/src/DRS9.Dashboard.Application/DTOs/ApplicationDto.cs @@ -15,9 +15,8 @@ public record ApplicationCreateRequest [MaxLength(1000)] public string? Description { get; set; } - [Required] [MaxLength(2000)] - public string ContentUrl { get; set; } = string.Empty; + public string? ContentUrl { get; set; } = string.Empty; // 轮播类型不需要 ContentUrl [MaxLength(500)] public string? ThumbnailUrl { get; set; } diff --git a/src/DRS9.Dashboard.Application/DTOs/RotatorItemDto.cs b/src/DRS9.Dashboard.Application/DTOs/RotatorItemDto.cs new file mode 100644 index 0000000..a2968b5 --- /dev/null +++ b/src/DRS9.Dashboard.Application/DTOs/RotatorItemDto.cs @@ -0,0 +1,66 @@ +using System.ComponentModel.DataAnnotations; + +namespace DRS9.Dashboard.Application.DTOs; + +/// +/// 轮播项 DTO +/// +public class RotatorItemDto +{ + public int Id { get; set; } + public int ApplicationId { get; set; } + public string ItemType { get; set; } = string.Empty; // Image, Video, Webpage + public string Url { get; set; } = string.Empty; + public int Duration { get; set; } = 10; // 显示时长(秒) + public int Order { get; set; } = 0; + public bool IsEnabled { get; set; } = true; +} + +/// +/// 创建轮播项请求 +/// +public class RotatorItemCreateRequest +{ + [Required] + public string ItemType { get; set; } = string.Empty; + + [Required] + [Url] + public string Url { get; set; } = string.Empty; + + [Range(1, 3600)] + public int Duration { get; set; } = 10; + + public int Order { get; set; } = 0; +} + +/// +/// 更新轮播项请求 +/// +public class RotatorItemUpdateRequest +{ + [Required] + public string ItemType { get; set; } = string.Empty; + + [Required] + [Url] + public string Url { get; set; } = string.Empty; + + [Range(1, 3600)] + public int Duration { get; set; } = 10; + + public int Order { get; set; } = 0; + + public bool IsEnabled { get; set; } = true; +} + +/// +/// 文件上传响应 +/// +public class FileUploadResponse +{ + public bool Success { get; set; } + public string? Message { get; set; } + public string? Url { get; set; } + public string? ItemType { get; set; } +} diff --git a/src/DRS9.Dashboard.Application/Services/ApplicationService.cs b/src/DRS9.Dashboard.Application/Services/ApplicationService.cs index 28c4210..2082e7a 100644 --- a/src/DRS9.Dashboard.Application/Services/ApplicationService.cs +++ b/src/DRS9.Dashboard.Application/Services/ApplicationService.cs @@ -46,12 +46,22 @@ public class ApplicationService public async Task CreateAsync(ApplicationCreateRequest request) { + // 验证:Dashboard 类型必须有 ContentUrl + if (request.Type == "Dashboard" && string.IsNullOrWhiteSpace(request.ContentUrl)) + { + return new ApplicationResponse + { + Success = false, + Message = "Dashboard 类型应用必须提供 URL" + }; + } + var application = new AppEntity { Name = request.Name, Type = request.Type, Description = request.Description, - ContentUrl = request.ContentUrl, + ContentUrl = request.ContentUrl ?? "", // 轮播类型可以为空 ThumbnailUrl = request.ThumbnailUrl, Priority = request.Priority, IsEnabled = true diff --git a/src/DRS9.Dashboard.Application/Services/DeviceManagementService.cs b/src/DRS9.Dashboard.Application/Services/DeviceManagementService.cs index 43a0525..1b463ed 100644 --- a/src/DRS9.Dashboard.Application/Services/DeviceManagementService.cs +++ b/src/DRS9.Dashboard.Application/Services/DeviceManagementService.cs @@ -212,7 +212,10 @@ public class DeviceManagementService ApplicationId = da.ApplicationId, ApplicationName = da.Application.Name, ApplicationType = da.Application.Type, - ContentUrl = da.Application.ContentUrl, + // 轮播类型使用轮播查看器 URL,其他类型使用应用的 ContentUrl + ContentUrl = da.Application.Type == "Rotator" + ? $"/rotator/{da.ApplicationId}" + : da.Application.ContentUrl, Order = da.Order, Duration = da.Duration }).ToList() diff --git a/src/DRS9.Dashboard.Application/Services/RotatorItemService.cs b/src/DRS9.Dashboard.Application/Services/RotatorItemService.cs new file mode 100644 index 0000000..3d6a1f0 --- /dev/null +++ b/src/DRS9.Dashboard.Application/Services/RotatorItemService.cs @@ -0,0 +1,169 @@ +using DRS9.Dashboard.Application.DTOs; +using DRS9.Dashboard.Domain.Entities; +using DRS9.Dashboard.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; + +namespace DRS9.Dashboard.Application.Services; + +public class RotatorItemService +{ + private readonly DashboardDbContext _context; + + public RotatorItemService(DashboardDbContext context) + { + _context = context; + } + + /// + /// 获取指定应用的所有轮播项 + /// + public async Task> GetByApplicationIdAsync(int applicationId) + { + return await _context.RotatorItems + .Where(ri => ri.ApplicationId == applicationId) + .OrderBy(ri => ri.Order) + .ThenBy(ri => ri.Id) + .Select(ri => new RotatorItemDto + { + Id = ri.Id, + ApplicationId = ri.ApplicationId, + ItemType = ri.ItemType, + Url = ri.Url, + Duration = ri.Duration, + Order = ri.Order, + IsEnabled = ri.IsEnabled + }) + .ToListAsync(); + } + + /// + /// 获取轮播项详情 + /// + public async Task GetByIdAsync(int id) + { + return await _context.RotatorItems + .Where(ri => ri.Id == id) + .Select(ri => new RotatorItemDto + { + Id = ri.Id, + ApplicationId = ri.ApplicationId, + ItemType = ri.ItemType, + Url = ri.Url, + Duration = ri.Duration, + Order = ri.Order, + IsEnabled = ri.IsEnabled + }) + .FirstOrDefaultAsync(); + } + + /// + /// 创建轮播项 + /// + public async Task CreateAsync(int applicationId, RotatorItemCreateRequest request) + { + // 验证应用是否存在且为 Rotator 类型 + var application = await _context.Applications.FindAsync(applicationId); + if (application == null) + return null; + if (application.Type != "Rotator") + throw new InvalidOperationException("只能为轮播类型的应用添加轮播项"); + + // 如果没有指定 Order,设置为当前最大值 + 1 + if (request.Order == 0) + { + var maxOrder = await _context.RotatorItems + .Where(ri => ri.ApplicationId == applicationId) + .MaxAsync(ri => (int?)ri.Order) ?? 0; + request.Order = maxOrder + 1; + } + + var rotatorItem = new RotatorItem + { + ApplicationId = applicationId, + ItemType = request.ItemType, + Url = request.Url, + Duration = request.Duration, + Order = request.Order, + IsEnabled = true, + CreatedAt = DateTime.UtcNow, + UpdatedAt = DateTime.UtcNow + }; + + try + { + _context.RotatorItems.Add(rotatorItem); + await _context.SaveChangesAsync(); + } + catch (Exception ex) + { + var innerEx = ex.InnerException; + string fullMessage = ex.Message; + while (innerEx != null) + { + fullMessage += " | Inner: " + innerEx.Message; + innerEx = innerEx.InnerException; + } + throw new InvalidOperationException(fullMessage, ex); + } + + return await GetByIdAsync(rotatorItem.Id); + } + + /// + /// 更新轮播项 + /// + public async Task UpdateAsync(int id, RotatorItemUpdateRequest request) + { + var rotatorItem = await _context.RotatorItems.FindAsync(id); + if (rotatorItem == null) + return null; + + rotatorItem.ItemType = request.ItemType; + rotatorItem.Url = request.Url; + rotatorItem.Duration = request.Duration; + rotatorItem.Order = request.Order; + rotatorItem.IsEnabled = request.IsEnabled; + rotatorItem.UpdatedAt = DateTime.UtcNow; + + await _context.SaveChangesAsync(); + + return await GetByIdAsync(id); + } + + /// + /// 删除轮播项 + /// + public async Task DeleteAsync(int id) + { + var rotatorItem = await _context.RotatorItems.FindAsync(id); + if (rotatorItem == null) + return false; + + _context.RotatorItems.Remove(rotatorItem); + await _context.SaveChangesAsync(); + return true; + } + + /// + /// 批量更新轮播项顺序 + /// + public async Task ReorderAsync(int applicationId, List itemIds) + { + var items = await _context.RotatorItems + .Where(ri => ri.ApplicationId == applicationId) + .ToListAsync(); + + for (int i = 0; i < itemIds.Count; i++) + { + var item = items.FirstOrDefault(x => x.Id == itemIds[i]); + if (item != null) + { + item.Order = i + 1; + item.UpdatedAt = DateTime.UtcNow; + } + } + + await _context.SaveChangesAsync(); + return true; + } +} diff --git a/src/DRS9.Dashboard.Domain/Entities/Application.cs b/src/DRS9.Dashboard.Domain/Entities/Application.cs index 1ff7ffa..2d5eefd 100644 --- a/src/DRS9.Dashboard.Domain/Entities/Application.cs +++ b/src/DRS9.Dashboard.Domain/Entities/Application.cs @@ -25,4 +25,5 @@ public class Application : BaseEntity // 导航属性 public ICollection Assignments { get; set; } = new List(); + public ICollection RotatorItems { get; set; } = new List(); } diff --git a/src/DRS9.Dashboard.Domain/Entities/RotatorItem.cs b/src/DRS9.Dashboard.Domain/Entities/RotatorItem.cs new file mode 100644 index 0000000..79cc419 --- /dev/null +++ b/src/DRS9.Dashboard.Domain/Entities/RotatorItem.cs @@ -0,0 +1,38 @@ +namespace DRS9.Dashboard.Domain.Entities; + +/// +/// 轮播项 - 用于轮播应用的图片、视频、网页条目 +/// +public class RotatorItem +{ + public int Id { get; set; } + public int ApplicationId { get; set; } + + /// + /// 条目类型:Image, Video, Webpage + /// + public string ItemType { get; set; } = string.Empty; + + /// + /// URL(图片、视频或网页的链接) + /// + public string Url { get; set; } = string.Empty; + + /// + /// 显示时长(秒) + /// + public int Duration { get; set; } = 10; + + /// + /// 排序顺序 + /// + public int Order { get; set; } = 0; + + /// + /// 是否启用 + /// + public bool IsEnabled { get; set; } = true; + + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/src/DRS9.Dashboard.Infrastructure/Data/DashboardDbContext.cs b/src/DRS9.Dashboard.Infrastructure/Data/DashboardDbContext.cs index a63a081..4ebcfaf 100644 --- a/src/DRS9.Dashboard.Infrastructure/Data/DashboardDbContext.cs +++ b/src/DRS9.Dashboard.Infrastructure/Data/DashboardDbContext.cs @@ -18,6 +18,7 @@ public class DashboardDbContext : DbContext public DbSet Playlists { get; set; } public DbSet PlaylistItems { get; set; } public DbSet AppVersions { get; set; } + public DbSet RotatorItems { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -101,5 +102,17 @@ public class DashboardDbContext : DbContext { entity.HasIndex(e => new { e.Platform, e.Version }); }); + + // RotatorItem 配置 + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + + // 不配置关系,只配置索引,避免 EF Core 创建影子属性 + // 外键约束在迁移中已定义,这里不再重复配置 + + // 同一应用中的轮播项按 Order 排序 + entity.HasIndex(e => new { e.ApplicationId, e.Order }); + }); } } diff --git a/src/DRS9.Dashboard.Infrastructure/Migrations/20260113093910_AddRotatorItemsTableV2.Designer.cs b/src/DRS9.Dashboard.Infrastructure/Migrations/20260113093910_AddRotatorItemsTableV2.Designer.cs new file mode 100644 index 0000000..9ded0bd --- /dev/null +++ b/src/DRS9.Dashboard.Infrastructure/Migrations/20260113093910_AddRotatorItemsTableV2.Designer.cs @@ -0,0 +1,543 @@ +// +using System; +using DRS9.Dashboard.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace DRS9.Dashboard.Infrastructure.Migrations +{ + [DbContext(typeof(DashboardDbContext))] + [Migration("20260113093910_AddRotatorItemsTableV2")] + partial class AddRotatorItemsTableV2 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.11"); + + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.AppVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChangeLog") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DownloadUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("FileSize") + .HasColumnType("INTEGER"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("IsForceUpdate") + .HasColumnType("INTEGER"); + + b.Property("Platform") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PublishedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("VersionName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Platform", "Version"); + + b.ToTable("AppVersions"); + }); + + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.Application", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ContentUrl") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("ThumbnailUrl") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.ToTable("Applications"); + }); + + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.AuditLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasColumnType("TEXT"); + + b.Property("EntityId") + .HasColumnType("INTEGER"); + + b.Property("EntityType") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Username") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("AuditLogs"); + }); + + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ActivatedAt") + .HasColumnType("TEXT"); + + b.Property("AppVersion") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeviceCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("DeviceGroupId") + .HasColumnType("INTEGER"); + + b.Property("DeviceName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("DeviceType") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("LastSeenAt") + .HasColumnType("TEXT"); + + b.Property("OsVersion") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeviceCode") + .IsUnique(); + + b.HasIndex("DeviceGroupId"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.DeviceAssignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApplicationId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeviceId") + .HasColumnType("INTEGER"); + + b.Property("Duration") + .HasColumnType("INTEGER"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("DeviceId", "ApplicationId") + .IsUnique(); + + b.ToTable("DeviceAssignments"); + }); + + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.DeviceGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("DeviceGroups"); + }); + + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.Playlist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("DeviceGroupId") + .HasColumnType("INTEGER"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("LoopMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeviceGroupId"); + + b.HasIndex("Name"); + + b.ToTable("Playlists"); + }); + + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.PlaylistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApplicationId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Duration") + .HasColumnType("INTEGER"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("PlaylistId") + .HasColumnType("INTEGER"); + + b.Property("ScheduleRule") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("PlaylistId", "Order"); + + b.ToTable("PlaylistItems"); + }); + + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.RotatorItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApplicationId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Duration") + .HasColumnType("INTEGER"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("ItemType") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Url") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Order"); + + b.ToTable("RotatorItems"); + }); + + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.Device", b => + { + b.HasOne("DRS9.Dashboard.Domain.Entities.DeviceGroup", "DeviceGroup") + .WithMany("Devices") + .HasForeignKey("DeviceGroupId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("DeviceGroup"); + }); + + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.DeviceAssignment", b => + { + b.HasOne("DRS9.Dashboard.Domain.Entities.Application", "Application") + .WithMany("Assignments") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DRS9.Dashboard.Domain.Entities.Device", "Device") + .WithMany("Assignments") + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Application"); + + b.Navigation("Device"); + }); + + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.Playlist", b => + { + b.HasOne("DRS9.Dashboard.Domain.Entities.DeviceGroup", "DeviceGroup") + .WithMany() + .HasForeignKey("DeviceGroupId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("DeviceGroup"); + }); + + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.PlaylistItem", b => + { + b.HasOne("DRS9.Dashboard.Domain.Entities.Application", "Application") + .WithMany() + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DRS9.Dashboard.Domain.Entities.Playlist", "Playlist") + .WithMany("Items") + .HasForeignKey("PlaylistId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Application"); + + b.Navigation("Playlist"); + }); + + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.RotatorItem", b => + { + b.HasOne("DRS9.Dashboard.Domain.Entities.Application", null) + .WithMany("RotatorItems") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.Application", b => + { + b.Navigation("Assignments"); + + b.Navigation("RotatorItems"); + }); + + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.Device", b => + { + b.Navigation("Assignments"); + }); + + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.DeviceGroup", b => + { + b.Navigation("Devices"); + }); + + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.Playlist", b => + { + b.Navigation("Items"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/DRS9.Dashboard.Infrastructure/Migrations/20260113093910_AddRotatorItemsTableV2.cs b/src/DRS9.Dashboard.Infrastructure/Migrations/20260113093910_AddRotatorItemsTableV2.cs new file mode 100644 index 0000000..15ab343 --- /dev/null +++ b/src/DRS9.Dashboard.Infrastructure/Migrations/20260113093910_AddRotatorItemsTableV2.cs @@ -0,0 +1,53 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DRS9.Dashboard.Infrastructure.Migrations +{ + /// + public partial class AddRotatorItemsTableV2 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "RotatorItems", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ApplicationId = table.Column(type: "INTEGER", nullable: false), + ItemType = table.Column(type: "TEXT", nullable: false), + Url = table.Column(type: "TEXT", nullable: false), + Duration = table.Column(type: "INTEGER", nullable: false), + Order = table.Column(type: "INTEGER", nullable: false), + IsEnabled = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RotatorItems", x => x.Id); + table.ForeignKey( + name: "FK_RotatorItems_Applications_ApplicationId", + column: x => x.ApplicationId, + principalTable: "Applications", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_RotatorItems_ApplicationId_Order", + table: "RotatorItems", + columns: new[] { "ApplicationId", "Order" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RotatorItems"); + } + } +} diff --git a/src/DRS9.Dashboard.Infrastructure/Migrations/DashboardDbContextModelSnapshot.cs b/src/DRS9.Dashboard.Infrastructure/Migrations/DashboardDbContextModelSnapshot.cs index 54aa119..49c70c5 100644 --- a/src/DRS9.Dashboard.Infrastructure/Migrations/DashboardDbContextModelSnapshot.cs +++ b/src/DRS9.Dashboard.Infrastructure/Migrations/DashboardDbContextModelSnapshot.cs @@ -361,6 +361,45 @@ namespace DRS9.Dashboard.Infrastructure.Migrations b.ToTable("PlaylistItems"); }); + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.RotatorItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApplicationId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Duration") + .HasColumnType("INTEGER"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("ItemType") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Url") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Order"); + + b.ToTable("RotatorItems"); + }); + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.User", b => { b.Property("Id") @@ -465,9 +504,20 @@ namespace DRS9.Dashboard.Infrastructure.Migrations b.Navigation("Playlist"); }); + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.RotatorItem", b => + { + b.HasOne("DRS9.Dashboard.Domain.Entities.Application", null) + .WithMany("RotatorItems") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.Application", b => { b.Navigation("Assignments"); + + b.Navigation("RotatorItems"); }); modelBuilder.Entity("DRS9.Dashboard.Domain.Entities.Device", b => diff --git a/src/DRS9.Dashboard.Server/Components/Layout/MainLayout.razor b/src/DRS9.Dashboard.Server/Components/Layout/MainLayout.razor index 4be661d..c926cbf 100644 --- a/src/DRS9.Dashboard.Server/Components/Layout/MainLayout.razor +++ b/src/DRS9.Dashboard.Server/Components/Layout/MainLayout.razor @@ -31,7 +31,7 @@