remove adapter for more ease of development

This commit is contained in:
2025-07-26 12:39:22 +02:00
parent 40bd228836
commit 4544be2999
13 changed files with 634 additions and 16 deletions

View File

@@ -1,6 +0,0 @@
namespace adapter;
public class Class1
{
}

View File

@@ -1,9 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

138
tagvid/AdapterContext.cs Normal file
View File

@@ -0,0 +1,138 @@
using Db.Models;
using Microsoft.EntityFrameworkCore;
public class AdapterContext : DbContext
{
public AdapterContext(DbContextOptions<AdapterContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Configure your entity mappings here
base.OnModelCreating(modelBuilder);
// primary keys
modelBuilder.Entity<Tag>().HasKey(t => t.Name);
modelBuilder.Entity<Video>().HasKey(v => v.Id);
modelBuilder.Entity<VideoMetadata>().HasKey(vm => vm.Id);
modelBuilder.Entity<Setting>().HasKey(s => s.Name);
// Id of VideoMetadata is the same as Video Id
modelBuilder.Entity<VideoMetadata>()
.Property(vm => vm.Id)
.ValueGeneratedNever(); // Id is set by the Video entity
// Id of Video is set by the database
modelBuilder.Entity<Video>()
.Property(v => v.Id)
.ValueGeneratedOnAdd(); // Id is generated by the database
// Id of Setting is the same as SettingName enum value
modelBuilder.Entity<Setting>()
.Property(s => s.Name)
.HasConversion<string>(); // Store enum as string in the database
// relationships
// Video can have one Metadata (can be null)
modelBuilder.Entity<Video>()
.HasOne(v => v.Metadata)
.WithOne(vm => vm.Video)
.HasForeignKey<VideoMetadata>(vm => vm.Id);
// Video can have many Tags
modelBuilder.Entity<Video>()
.HasMany(v => v.Tags)
.WithMany(t => t.Videos)
.UsingEntity(j => j.ToTable("VideoTags")); // Join table for many-to-many relationship
// Setting has a unique Name
modelBuilder.Entity<Setting>()
.HasIndex(s => s.Name)
.IsUnique();
// Setting name enum is stored as string
modelBuilder.Entity<Setting>()
.Property(s => s.Name)
.HasConversion<string>();
// Setting value is stored as string
modelBuilder.Entity<Setting>()
.Property(s => s.Value)
.IsRequired();
// Video relative path is required
modelBuilder.Entity<Video>()
.Property(v => v.RelativePath)
.IsRequired();
// VideoMetadata properties
modelBuilder.Entity<VideoMetadata>()
.Property(vm => vm.Title)
.IsRequired();
modelBuilder.Entity<VideoMetadata>()
.Property(vm => vm.Duration)
.IsRequired();
// VideoMetadata duration is defaulted to zero if not set
modelBuilder.Entity<VideoMetadata>()
.Property(vm => vm.Duration)
.HasDefaultValue(TimeSpan.Zero);
// VideoMetadata width and height are required
modelBuilder.Entity<VideoMetadata>()
.Property(vm => vm.Width)
.IsRequired();
modelBuilder.Entity<VideoMetadata>()
.Property(vm => vm.Height)
.IsRequired();
// Tag name is required
modelBuilder.Entity<Tag>()
.Property(t => t.Name)
.IsRequired();
// Tag name is unique
modelBuilder.Entity<Tag>()
.HasIndex(t => t.Name)
.IsUnique();
}
// Tags a video can have (e.g. "funny", "cat", "dog")
public DbSet<Tag> Tags { get; set; }
// Videos with tags, relative paths, and other metadata
public DbSet<Video> Videos { get; set; }
// Video metadata, such as title, description, and duration
public DbSet<VideoMetadata> VideoMetadatas { get; set; }
// Settings for the adapter, such as video path
public DbSet<Setting> Settings { get; set; }
public async Task<T?> GetSettingAsync<T>(SettingName settingName)
{
var setting = await Settings.FindAsync(settingName);
return setting != null ? (T?)Convert.ChangeType(setting.Value, typeof(T)) : default;
}
public async Task SetSettingAsync<T>(SettingName settingName, T value)
{
var setting = await Settings.FindAsync(settingName);
if (setting == null)
{
setting = new Setting { Name = settingName, Value = value?.ToString() ?? string.Empty };
Settings.Add(setting);
}
else
{
setting.Value = value?.ToString() ?? string.Empty;
Settings.Update(setting);
}
await SaveChangesAsync();
}
}

View File

@@ -0,0 +1,145 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace tagvid.Migrations
{
[DbContext(typeof(AdapterContext))]
[Migration("20250726102958_InitialMigration")]
partial class InitialMigration
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.7")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Db.Models.Setting", b =>
{
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Name");
b.HasIndex("Name")
.IsUnique();
b.ToTable("Settings");
});
modelBuilder.Entity("Db.Models.Tag", b =>
{
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.HasKey("Name");
b.HasIndex("Name")
.IsUnique();
b.ToTable("Tags");
});
modelBuilder.Entity("Db.Models.Video", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("nvarchar(450)");
b.Property<string>("RelativePath")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Videos");
});
modelBuilder.Entity("Db.Models.VideoMetadata", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<TimeSpan>("Duration")
.ValueGeneratedOnAdd()
.HasColumnType("time")
.HasDefaultValue(new TimeSpan(0, 0, 0, 0, 0));
b.Property<int>("Height")
.HasColumnType("int");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("Width")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("VideoMetadatas");
});
modelBuilder.Entity("TagVideo", b =>
{
b.Property<string>("TagsName")
.HasColumnType("nvarchar(450)");
b.Property<string>("VideosId")
.HasColumnType("nvarchar(450)");
b.HasKey("TagsName", "VideosId");
b.HasIndex("VideosId");
b.ToTable("VideoTags", (string)null);
});
modelBuilder.Entity("Db.Models.VideoMetadata", b =>
{
b.HasOne("Db.Models.Video", "Video")
.WithOne("Metadata")
.HasForeignKey("Db.Models.VideoMetadata", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Video");
});
modelBuilder.Entity("TagVideo", b =>
{
b.HasOne("Db.Models.Tag", null)
.WithMany()
.HasForeignKey("TagsName")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Db.Models.Video", null)
.WithMany()
.HasForeignKey("VideosId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Db.Models.Video", b =>
{
b.Navigation("Metadata");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,131 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace tagvid.Migrations
{
/// <inheritdoc />
public partial class InitialMigration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Settings",
columns: table => new
{
Name = table.Column<string>(type: "nvarchar(450)", nullable: false),
Value = table.Column<string>(type: "nvarchar(max)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Settings", x => x.Name);
});
migrationBuilder.CreateTable(
name: "Tags",
columns: table => new
{
Name = table.Column<string>(type: "nvarchar(450)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Tags", x => x.Name);
});
migrationBuilder.CreateTable(
name: "Videos",
columns: table => new
{
Id = table.Column<string>(type: "nvarchar(450)", nullable: false),
RelativePath = table.Column<string>(type: "nvarchar(max)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Videos", x => x.Id);
});
migrationBuilder.CreateTable(
name: "VideoMetadatas",
columns: table => new
{
Id = table.Column<string>(type: "nvarchar(450)", nullable: false),
Title = table.Column<string>(type: "nvarchar(max)", nullable: false),
Duration = table.Column<TimeSpan>(type: "time", nullable: false, defaultValue: new TimeSpan(0, 0, 0, 0, 0)),
Width = table.Column<int>(type: "int", nullable: false),
Height = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_VideoMetadatas", x => x.Id);
table.ForeignKey(
name: "FK_VideoMetadatas_Videos_Id",
column: x => x.Id,
principalTable: "Videos",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "VideoTags",
columns: table => new
{
TagsName = table.Column<string>(type: "nvarchar(450)", nullable: false),
VideosId = table.Column<string>(type: "nvarchar(450)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_VideoTags", x => new { x.TagsName, x.VideosId });
table.ForeignKey(
name: "FK_VideoTags_Tags_TagsName",
column: x => x.TagsName,
principalTable: "Tags",
principalColumn: "Name",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_VideoTags_Videos_VideosId",
column: x => x.VideosId,
principalTable: "Videos",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Settings_Name",
table: "Settings",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Tags_Name",
table: "Tags",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_VideoTags_VideosId",
table: "VideoTags",
column: "VideosId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Settings");
migrationBuilder.DropTable(
name: "VideoMetadatas");
migrationBuilder.DropTable(
name: "VideoTags");
migrationBuilder.DropTable(
name: "Tags");
migrationBuilder.DropTable(
name: "Videos");
}
}
}

View File

@@ -0,0 +1,142 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace tagvid.Migrations
{
[DbContext(typeof(AdapterContext))]
partial class AdapterContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.7")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Db.Models.Setting", b =>
{
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Name");
b.HasIndex("Name")
.IsUnique();
b.ToTable("Settings");
});
modelBuilder.Entity("Db.Models.Tag", b =>
{
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.HasKey("Name");
b.HasIndex("Name")
.IsUnique();
b.ToTable("Tags");
});
modelBuilder.Entity("Db.Models.Video", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("nvarchar(450)");
b.Property<string>("RelativePath")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Videos");
});
modelBuilder.Entity("Db.Models.VideoMetadata", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<TimeSpan>("Duration")
.ValueGeneratedOnAdd()
.HasColumnType("time")
.HasDefaultValue(new TimeSpan(0, 0, 0, 0, 0));
b.Property<int>("Height")
.HasColumnType("int");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("Width")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("VideoMetadatas");
});
modelBuilder.Entity("TagVideo", b =>
{
b.Property<string>("TagsName")
.HasColumnType("nvarchar(450)");
b.Property<string>("VideosId")
.HasColumnType("nvarchar(450)");
b.HasKey("TagsName", "VideosId");
b.HasIndex("VideosId");
b.ToTable("VideoTags", (string)null);
});
modelBuilder.Entity("Db.Models.VideoMetadata", b =>
{
b.HasOne("Db.Models.Video", "Video")
.WithOne("Metadata")
.HasForeignKey("Db.Models.VideoMetadata", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Video");
});
modelBuilder.Entity("TagVideo", b =>
{
b.HasOne("Db.Models.Tag", null)
.WithMany()
.HasForeignKey("TagsName")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Db.Models.Video", null)
.WithMany()
.HasForeignKey("VideosId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Db.Models.Video", b =>
{
b.Navigation("Metadata");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -1,8 +1,14 @@
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
// Configure Entity Framework Core with SQL Server
builder.Services.AddDbContext<AdapterContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
var app = builder.Build();
// Configure the HTTP request pipeline.

8
tagvid/SettingName.cs Normal file
View File

@@ -0,0 +1,8 @@
public enum SettingName
{
/// <summary>
/// Represents a the absolute path of the folder where videos are stored.
/// </summary>
VideoPath,
}

View File

@@ -0,0 +1,13 @@
namespace Db.Models;
public class Setting
{
public required SettingName Name { get; set; }
public required string Value { get; set; }
// methods to convert Value to specific types that are not connected to entity framework
public T GetValue<T>()
{
return (T)Convert.ChangeType(Value, typeof(T));
}
}

9
tagvid/db/models/Tag.cs Normal file
View File

@@ -0,0 +1,9 @@
namespace Db.Models;
public class Tag
{
// We use the name of the tag as the Id to ensure uniqueness
public required string Name { get; set; }
public ICollection<Video> Videos { get; set; } = [];
}

15
tagvid/db/models/Video.cs Normal file
View File

@@ -0,0 +1,15 @@
namespace Db.Models;
public class Video
{
// Disable warning as Id is required but not set in the constructor.
// This is to ensure that the Id is set by the database when the entity is added
#pragma warning disable CS8618
public string Id { get; set; }
#pragma warning restore CS8618
public required string RelativePath { get; set; }
public ICollection<Tag> Tags { get; set; } = [];
public VideoMetadata? Metadata { get; set; }
}

View File

@@ -0,0 +1,20 @@
namespace Db.Models;
/// <summary>
/// Represents metadata for a video, such as title, duration, resolution, etc.
/// So we dont need to load the video file to get this information.
/// </summary>
public class VideoMetadata
{
// Disable warning as Id is required but not set in the constructor.
// This is to ensure that the Id is set by the database when the entity is added
#pragma warning disable CS8618
public string Id { get; set; }
#pragma warning restore CS8618
public required string Title { get; set; }
public TimeSpan Duration { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public Video? Video { get; set; }
}

View File

@@ -1,7 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<ItemGroup>
<ProjectReference Include="..\adapter\adapter.csproj" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.7" />
</ItemGroup>
<PropertyGroup>