feat: 视频缩略图生成、最近文件面板与前端视图重构
- 新增 VideoThumbnailService,基于 ffmpeg 截取视频缩略图,ffprobe 提取时长
- 新增 ManagedThumbnailMap 模型及多数据库迁移,存储缩略图元数据
- 新增 /api/thumbnails/{id} 缩略图流端点
- 新增最近添加/最近播放 API 与前端面板,支持列表/网格双视图切换
- FileRecordDto 扩展 thumbnailUrl、videoDuration、lastPlayedAt 字段
- 前端新增文件库 Tab 导航、卡片网格视图、视频海报与时长信息栏
- 添加文件库目录不再同步全量扫描,改为后台异步自动扫描
This commit is contained in:
parent
6acc92ca27
commit
2c20f9bb54
1
.gitignore
vendored
1
.gitignore
vendored
@ -32,3 +32,4 @@
|
|||||||
/bin
|
/bin
|
||||||
/obj
|
/obj
|
||||||
/.claude
|
/.claude
|
||||||
|
/.codex-build
|
||||||
|
|||||||
@ -40,6 +40,14 @@ namespace FileShare_API.Configuration
|
|||||||
|
|
||||||
// ---- 业务服务 ----
|
// ---- 业务服务 ----
|
||||||
services.AddScoped<WeatherForecastService>();
|
services.AddScoped<WeatherForecastService>();
|
||||||
|
var thumbnailOptions = configuration
|
||||||
|
.GetSection(nameof(ThumbnailStorageOptions))
|
||||||
|
.Get<ThumbnailStorageOptions>()
|
||||||
|
?? new ThumbnailStorageOptions();
|
||||||
|
services.AddSingleton(thumbnailOptions);
|
||||||
|
services.AddSingleton<IVideoThumbnailService>(sp =>
|
||||||
|
new VideoThumbnailService(sp.GetRequiredService<ThumbnailStorageOptions>()));
|
||||||
|
services.AddScoped<IThumbnailStreamService, ThumbnailStreamService>();
|
||||||
services.AddScoped<IFileLibraryService, FileLibraryService>();
|
services.AddScoped<IFileLibraryService, FileLibraryService>();
|
||||||
services.AddScoped<IFileLibraryEndpointService, FileLibraryEndpointService>();
|
services.AddScoped<IFileLibraryEndpointService, FileLibraryEndpointService>();
|
||||||
services.AddScoped<IFileStreamService, FileStreamService>();
|
services.AddScoped<IFileStreamService, FileStreamService>();
|
||||||
|
|||||||
@ -43,6 +43,35 @@ namespace FileShare_API.Extensions
|
|||||||
.WithName("StreamManagedFileById")
|
.WithName("StreamManagedFileById")
|
||||||
.WithTags("FileLibrary");
|
.WithTags("FileLibrary");
|
||||||
|
|
||||||
|
app.MapMethods(
|
||||||
|
"/api/thumbnails/{id:int}",
|
||||||
|
["GET", "HEAD"],
|
||||||
|
async (int id, IThumbnailStreamService thumbnailStreamService, HttpContext httpContext) =>
|
||||||
|
{
|
||||||
|
var thumbnail = await thumbnailStreamService.GetThumbnailAsync(id);
|
||||||
|
if (thumbnail is null)
|
||||||
|
{
|
||||||
|
return Results.NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
var stream = System.IO.File.Open(
|
||||||
|
thumbnail.FilePath,
|
||||||
|
FileMode.Open,
|
||||||
|
FileAccess.Read,
|
||||||
|
FileShare.ReadWrite);
|
||||||
|
|
||||||
|
httpContext.Response.Headers.ContentDisposition =
|
||||||
|
$"inline; filename=\"{Uri.EscapeDataString(thumbnail.FileName)}\"";
|
||||||
|
httpContext.Response.Headers.CacheControl = "public, max-age=3600";
|
||||||
|
|
||||||
|
return Results.File(
|
||||||
|
stream,
|
||||||
|
contentType: thumbnail.ContentType,
|
||||||
|
lastModified: thumbnail.LastModified);
|
||||||
|
})
|
||||||
|
.WithName("StreamManagedThumbnailById")
|
||||||
|
.WithTags("FileLibrary");
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,8 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Controllers\" />
|
<Folder Include="Controllers\" />
|
||||||
|
<Content Include="..\tools\ffmpeg\bin\ffmpeg.exe" Link="tools\ffmpeg\bin\ffmpeg.exe" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
|
||||||
|
<Content Include="..\tools\ffmpeg\bin\ffprobe.exe" Link="tools\ffmpeg\bin\ffprobe.exe" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="RestoreFrontendPackages" BeforeTargets="Build" Condition="'$(SkipFrontendBuild)' != 'true' And Exists('..\FileShare-Web-VUE\package.json') And !Exists('..\FileShare-Web-VUE\node_modules')">
|
<Target Name="RestoreFrontendPackages" BeforeTargets="Build" Condition="'$(SkipFrontendBuild)' != 'true' And Exists('..\FileShare-Web-VUE\package.json') And !Exists('..\FileShare-Web-VUE\node_modules')">
|
||||||
|
|||||||
@ -24,5 +24,10 @@
|
|||||||
"RecreateDatabase": false,
|
"RecreateDatabase": false,
|
||||||
"EnableDetailedLog": false,
|
"EnableDetailedLog": false,
|
||||||
"Timeout": 30
|
"Timeout": 30
|
||||||
|
},
|
||||||
|
"ThumbnailStorageOptions": {
|
||||||
|
"RootPath": "thumbnails",
|
||||||
|
"FfmpegPath": "tools/ffmpeg/bin/ffmpeg.exe",
|
||||||
|
"FfprobePath": "tools/ffmpeg/bin/ffprobe.exe"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,8 @@ namespace FileShare_EFCore.Database
|
|||||||
/// <summary>文件库文件记录数据</summary>
|
/// <summary>文件库文件记录数据</summary>
|
||||||
public DbSet<ManagedFileRecord> ManagedFileRecords => Set<ManagedFileRecord>();
|
public DbSet<ManagedFileRecord> ManagedFileRecords => Set<ManagedFileRecord>();
|
||||||
|
|
||||||
|
public DbSet<ManagedThumbnailMap> ManagedThumbnailMaps => Set<ManagedThumbnailMap>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 配置实体映射,包括主键、索引和属性约束。
|
/// 配置实体映射,包括主键、索引和属性约束。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -66,8 +68,10 @@ namespace FileShare_EFCore.Database
|
|||||||
{
|
{
|
||||||
entity.HasKey(e => e.Id).HasName("pk-managed-file-record");
|
entity.HasKey(e => e.Id).HasName("pk-managed-file-record");
|
||||||
entity.HasIndex(e => e.LibraryRootId).HasDatabaseName("idx-managed-file-record-root-id");
|
entity.HasIndex(e => e.LibraryRootId).HasDatabaseName("idx-managed-file-record-root-id");
|
||||||
|
entity.HasIndex(e => e.ThumbnailId).HasDatabaseName("idx-managed-file-record-thumbnail-id");
|
||||||
entity.HasIndex(e => e.AbsolutePath).IsUnique().HasDatabaseName("idx-managed-file-record-absolute-path");
|
entity.HasIndex(e => e.AbsolutePath).IsUnique().HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||||
entity.HasIndex(e => new { e.MediaType, e.Exists }).HasDatabaseName("idx-managed-file-record-media-type-exists");
|
entity.HasIndex(e => new { e.MediaType, e.Exists }).HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||||
|
entity.HasIndex(e => e.LastPlayedAt).HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||||
entity.Property(e => e.FileName).HasMaxLength(260);
|
entity.Property(e => e.FileName).HasMaxLength(260);
|
||||||
entity.Property(e => e.RelativePath).HasMaxLength(1024);
|
entity.Property(e => e.RelativePath).HasMaxLength(1024);
|
||||||
entity.Property(e => e.AbsolutePath).HasMaxLength(2048);
|
entity.Property(e => e.AbsolutePath).HasMaxLength(2048);
|
||||||
@ -78,6 +82,23 @@ namespace FileShare_EFCore.Database
|
|||||||
.WithMany(e => e.Files)
|
.WithMany(e => e.Files)
|
||||||
.HasForeignKey(e => e.LibraryRootId)
|
.HasForeignKey(e => e.LibraryRootId)
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
entity.HasOne(e => e.Thumbnail)
|
||||||
|
.WithMany(e => e.Files)
|
||||||
|
.HasForeignKey(e => e.ThumbnailId)
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<ManagedThumbnailMap>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id).HasName("pk-managed-thumbnail-map");
|
||||||
|
entity.HasIndex(e => e.LibraryRootId).HasDatabaseName("idx-managed-thumbnail-map-root-id");
|
||||||
|
entity.HasIndex(e => e.RelativePath).IsUnique().HasDatabaseName("idx-managed-thumbnail-map-relative-path");
|
||||||
|
entity.Property(e => e.RelativePath).HasMaxLength(1024);
|
||||||
|
entity.Property(e => e.ContentType).HasMaxLength(100);
|
||||||
|
entity.HasOne(e => e.LibraryRoot)
|
||||||
|
.WithMany(e => e.Thumbnails)
|
||||||
|
.HasForeignKey(e => e.LibraryRootId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
370
FileShare-EFCore/Migrations/MySQL/20260522082856_AutoMigration_20260522162758.Designer.cs
generated
Normal file
370
FileShare-EFCore/Migrations/MySQL/20260522082856_AutoMigration_20260522162758.Designer.cs
generated
Normal file
@ -0,0 +1,370 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using FileShare_EFCore.Database;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FileShare_EFCore.Migrations.MySQL
|
||||||
|
{
|
||||||
|
[DbContext(typeof(MySqlAppDataContext))]
|
||||||
|
[Migration("20260522082856_AutoMigration_20260522162758")]
|
||||||
|
partial class AutoMigration_20260522162758
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.7")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<string>("Device")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)")
|
||||||
|
.HasColumnName("device");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExpiresAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("expires-at");
|
||||||
|
|
||||||
|
b.Property<string>("IpAddress")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("varchar(64)")
|
||||||
|
.HasColumnName("ip-address");
|
||||||
|
|
||||||
|
b.Property<string>("ReplacedByTokenHash")
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("varchar(128)")
|
||||||
|
.HasColumnName("replaced-by-token-hash");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("RevokedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("revoked-at");
|
||||||
|
|
||||||
|
b.Property<string>("TokenHash")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("varchar(128)")
|
||||||
|
.HasColumnName("token-hash");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("user-id");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-api-refresh-token");
|
||||||
|
|
||||||
|
b.HasIndex("TokenHash")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-api-refresh-token-hash");
|
||||||
|
|
||||||
|
b.HasIndex("UserId")
|
||||||
|
.HasDatabaseName("idx-api-refresh-token-user-id");
|
||||||
|
|
||||||
|
b.ToTable("api-refresh-token", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("API refresh token");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("AbsolutePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("varchar(2048)")
|
||||||
|
.HasColumnName("absolute-path");
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)")
|
||||||
|
.HasColumnName("content-type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<bool>("Exists")
|
||||||
|
.HasColumnType("tinyint(1)")
|
||||||
|
.HasColumnName("exists");
|
||||||
|
|
||||||
|
b.Property<string>("Extension")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("varchar(32)")
|
||||||
|
.HasColumnName("extension");
|
||||||
|
|
||||||
|
b.Property<string>("FileName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(260)
|
||||||
|
.HasColumnType("varchar(260)")
|
||||||
|
.HasColumnName("file-name");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastPlayedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("last-played-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastSeenAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("last-seen-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastWriteTimeUtc")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("last-write-time-utc");
|
||||||
|
|
||||||
|
b.Property<int>("LibraryRootId")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("library-root-id");
|
||||||
|
|
||||||
|
b.Property<string>("MediaType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("varchar(20)")
|
||||||
|
.HasColumnName("media-type");
|
||||||
|
|
||||||
|
b.Property<string>("RelativePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("varchar(1024)")
|
||||||
|
.HasColumnName("relative-path");
|
||||||
|
|
||||||
|
b.Property<long>("SizeBytes")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("size-bytes");
|
||||||
|
|
||||||
|
b.Property<string>("ThumbnailPath")
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("varchar(512)")
|
||||||
|
.HasColumnName("thumbnail-path");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.Property<double?>("VideoDuration")
|
||||||
|
.HasColumnType("double")
|
||||||
|
.HasColumnName("video-duration");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-file-record");
|
||||||
|
|
||||||
|
b.HasIndex("AbsolutePath")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||||
|
|
||||||
|
b.HasIndex("LastPlayedAt")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||||
|
|
||||||
|
b.HasIndex("LibraryRootId")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||||
|
|
||||||
|
b.HasIndex("MediaType", "Exists")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||||
|
|
||||||
|
b.ToTable("managed-file-record", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库文件记录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)")
|
||||||
|
.HasColumnName("display-name");
|
||||||
|
|
||||||
|
b.Property<bool>("IsAvailable")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("tinyint(1)")
|
||||||
|
.HasDefaultValue(true)
|
||||||
|
.HasColumnName("is-available");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEnabled")
|
||||||
|
.HasColumnType("tinyint(1)")
|
||||||
|
.HasColumnName("is-enabled");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanCompletedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("last-scan-completed-at");
|
||||||
|
|
||||||
|
b.Property<string>("LastScanError")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("varchar(2000)")
|
||||||
|
.HasColumnName("last-scan-error");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanStartedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("last-scan-started-at");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("varchar(1024)")
|
||||||
|
.HasColumnName("path");
|
||||||
|
|
||||||
|
b.Property<int>("ScanIntervalMinutes")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("scan-interval-minutes");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-library-root");
|
||||||
|
|
||||||
|
b.HasIndex("Path")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-library-root-path");
|
||||||
|
|
||||||
|
b.ToTable("managed-library-root", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库根目录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id")
|
||||||
|
.HasComment("用户主键");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("created-at")
|
||||||
|
.HasComment("创建时间");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)")
|
||||||
|
.HasColumnName("email")
|
||||||
|
.HasComment("用户邮箱");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)")
|
||||||
|
.HasColumnName("name")
|
||||||
|
.HasComment("用户名称");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)")
|
||||||
|
.HasColumnName("password-hash")
|
||||||
|
.HasComment("密码哈希值");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("varchar(50)")
|
||||||
|
.HasColumnName("phone-number")
|
||||||
|
.HasComment("电话号码");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("updated-at")
|
||||||
|
.HasComment("更新时间");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-user");
|
||||||
|
|
||||||
|
b.ToTable("user", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("用户实体,演示数据库 CRUD 操作");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id")
|
||||||
|
.HasComment("天气预报主键");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("created-at")
|
||||||
|
.HasComment("创建时间");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("Date")
|
||||||
|
.HasColumnType("date")
|
||||||
|
.HasColumnName("date")
|
||||||
|
.HasComment("预报日期");
|
||||||
|
|
||||||
|
b.Property<string>("Summary")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)")
|
||||||
|
.HasColumnName("summary")
|
||||||
|
.HasComment("天气摘要");
|
||||||
|
|
||||||
|
b.Property<int>("TemperatureC")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("temperature-c")
|
||||||
|
.HasComment("摄氏温度");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("updated-at")
|
||||||
|
.HasComment("更新时间");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-weather-forecast");
|
||||||
|
|
||||||
|
b.ToTable("weather-forecast", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("天气预报数据实体");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("LibraryRootId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("LibraryRoot");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,113 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using MySql.EntityFrameworkCore.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FileShare_EFCore.Migrations.MySQL
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AutoMigration_20260522162758 : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "managed-library-root",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn),
|
||||||
|
path = table.Column<string>(type: "varchar(1024)", maxLength: 1024, nullable: false),
|
||||||
|
displayname = table.Column<string>(name: "display-name", type: "varchar(200)", maxLength: 200, nullable: false),
|
||||||
|
isenabled = table.Column<bool>(name: "is-enabled", type: "tinyint(1)", nullable: false),
|
||||||
|
isavailable = table.Column<bool>(name: "is-available", type: "tinyint(1)", nullable: false, defaultValue: true),
|
||||||
|
scanintervalminutes = table.Column<int>(name: "scan-interval-minutes", type: "int", nullable: false),
|
||||||
|
lastscanstartedat = table.Column<DateTime>(name: "last-scan-started-at", type: "datetime(6)", nullable: true),
|
||||||
|
lastscancompletedat = table.Column<DateTime>(name: "last-scan-completed-at", type: "datetime(6)", nullable: true),
|
||||||
|
lastscanerror = table.Column<string>(name: "last-scan-error", type: "varchar(2000)", maxLength: 2000, nullable: true),
|
||||||
|
createdat = table.Column<DateTime>(name: "created-at", type: "datetime(6)", nullable: false),
|
||||||
|
updatedat = table.Column<DateTime>(name: "updated-at", type: "datetime(6)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk-managed-library-root", x => x.id);
|
||||||
|
},
|
||||||
|
comment: "文件库根目录")
|
||||||
|
.Annotation("MySQL:Charset", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "managed-file-record",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn),
|
||||||
|
libraryrootid = table.Column<int>(name: "library-root-id", type: "int", nullable: false),
|
||||||
|
filename = table.Column<string>(name: "file-name", type: "varchar(260)", maxLength: 260, nullable: false),
|
||||||
|
relativepath = table.Column<string>(name: "relative-path", type: "varchar(1024)", maxLength: 1024, nullable: false),
|
||||||
|
absolutepath = table.Column<string>(name: "absolute-path", type: "varchar(2048)", maxLength: 2048, nullable: false),
|
||||||
|
extension = table.Column<string>(type: "varchar(32)", maxLength: 32, nullable: false),
|
||||||
|
sizebytes = table.Column<long>(name: "size-bytes", type: "bigint", nullable: false),
|
||||||
|
lastwritetimeutc = table.Column<DateTime>(name: "last-write-time-utc", type: "datetime(6)", nullable: false),
|
||||||
|
mediatype = table.Column<string>(name: "media-type", type: "varchar(20)", maxLength: 20, nullable: false),
|
||||||
|
contenttype = table.Column<string>(name: "content-type", type: "varchar(100)", maxLength: 100, nullable: false),
|
||||||
|
exists = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||||
|
lastseenat = table.Column<DateTime>(name: "last-seen-at", type: "datetime(6)", nullable: false),
|
||||||
|
thumbnailpath = table.Column<string>(name: "thumbnail-path", type: "varchar(512)", maxLength: 512, nullable: true),
|
||||||
|
videoduration = table.Column<double>(name: "video-duration", type: "double", nullable: true),
|
||||||
|
lastplayedat = table.Column<DateTime>(name: "last-played-at", type: "datetime(6)", nullable: true),
|
||||||
|
createdat = table.Column<DateTime>(name: "created-at", type: "datetime(6)", nullable: false),
|
||||||
|
updatedat = table.Column<DateTime>(name: "updated-at", type: "datetime(6)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk-managed-file-record", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_managed-file-record_managed-library-root_library-root-id",
|
||||||
|
column: x => x.libraryrootid,
|
||||||
|
principalTable: "managed-library-root",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
},
|
||||||
|
comment: "文件库文件记录")
|
||||||
|
.Annotation("MySQL:Charset", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-file-record-absolute-path",
|
||||||
|
table: "managed-file-record",
|
||||||
|
column: "absolute-path",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-file-record-last-played-at",
|
||||||
|
table: "managed-file-record",
|
||||||
|
column: "last-played-at");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-file-record-media-type-exists",
|
||||||
|
table: "managed-file-record",
|
||||||
|
columns: new[] { "media-type", "exists" });
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-file-record-root-id",
|
||||||
|
table: "managed-file-record",
|
||||||
|
column: "library-root-id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-library-root-path",
|
||||||
|
table: "managed-library-root",
|
||||||
|
column: "path",
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "managed-library-root");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
444
FileShare-EFCore/Migrations/MySQL/20260522084325_AddThumbnailMap.Designer.cs
generated
Normal file
444
FileShare-EFCore/Migrations/MySQL/20260522084325_AddThumbnailMap.Designer.cs
generated
Normal file
@ -0,0 +1,444 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using FileShare_EFCore.Database;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FileShare_EFCore.Migrations.MySQL
|
||||||
|
{
|
||||||
|
[DbContext(typeof(MySqlAppDataContext))]
|
||||||
|
[Migration("20260522084325_AddThumbnailMap")]
|
||||||
|
partial class AddThumbnailMap
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.7")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<string>("Device")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)")
|
||||||
|
.HasColumnName("device");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExpiresAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("expires-at");
|
||||||
|
|
||||||
|
b.Property<string>("IpAddress")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("varchar(64)")
|
||||||
|
.HasColumnName("ip-address");
|
||||||
|
|
||||||
|
b.Property<string>("ReplacedByTokenHash")
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("varchar(128)")
|
||||||
|
.HasColumnName("replaced-by-token-hash");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("RevokedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("revoked-at");
|
||||||
|
|
||||||
|
b.Property<string>("TokenHash")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("varchar(128)")
|
||||||
|
.HasColumnName("token-hash");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("user-id");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-api-refresh-token");
|
||||||
|
|
||||||
|
b.HasIndex("TokenHash")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-api-refresh-token-hash");
|
||||||
|
|
||||||
|
b.HasIndex("UserId")
|
||||||
|
.HasDatabaseName("idx-api-refresh-token-user-id");
|
||||||
|
|
||||||
|
b.ToTable("api-refresh-token", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("API refresh token");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("AbsolutePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("varchar(2048)")
|
||||||
|
.HasColumnName("absolute-path");
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)")
|
||||||
|
.HasColumnName("content-type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<bool>("Exists")
|
||||||
|
.HasColumnType("tinyint(1)")
|
||||||
|
.HasColumnName("exists");
|
||||||
|
|
||||||
|
b.Property<string>("Extension")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("varchar(32)")
|
||||||
|
.HasColumnName("extension");
|
||||||
|
|
||||||
|
b.Property<string>("FileName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(260)
|
||||||
|
.HasColumnType("varchar(260)")
|
||||||
|
.HasColumnName("file-name");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastPlayedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("last-played-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastSeenAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("last-seen-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastWriteTimeUtc")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("last-write-time-utc");
|
||||||
|
|
||||||
|
b.Property<int>("LibraryRootId")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("library-root-id");
|
||||||
|
|
||||||
|
b.Property<string>("MediaType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("varchar(20)")
|
||||||
|
.HasColumnName("media-type");
|
||||||
|
|
||||||
|
b.Property<string>("RelativePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("varchar(1024)")
|
||||||
|
.HasColumnName("relative-path");
|
||||||
|
|
||||||
|
b.Property<long>("SizeBytes")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("size-bytes");
|
||||||
|
|
||||||
|
b.Property<int?>("ThumbnailId")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("thumbnail-id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.Property<double?>("VideoDuration")
|
||||||
|
.HasColumnType("double")
|
||||||
|
.HasColumnName("video-duration");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-file-record");
|
||||||
|
|
||||||
|
b.HasIndex("AbsolutePath")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||||
|
|
||||||
|
b.HasIndex("LastPlayedAt")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||||
|
|
||||||
|
b.HasIndex("LibraryRootId")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||||
|
|
||||||
|
b.HasIndex("ThumbnailId")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-thumbnail-id");
|
||||||
|
|
||||||
|
b.HasIndex("MediaType", "Exists")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||||
|
|
||||||
|
b.ToTable("managed-file-record", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库文件记录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)")
|
||||||
|
.HasColumnName("display-name");
|
||||||
|
|
||||||
|
b.Property<bool>("IsAvailable")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("tinyint(1)")
|
||||||
|
.HasDefaultValue(true)
|
||||||
|
.HasColumnName("is-available");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEnabled")
|
||||||
|
.HasColumnType("tinyint(1)")
|
||||||
|
.HasColumnName("is-enabled");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanCompletedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("last-scan-completed-at");
|
||||||
|
|
||||||
|
b.Property<string>("LastScanError")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("varchar(2000)")
|
||||||
|
.HasColumnName("last-scan-error");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanStartedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("last-scan-started-at");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("varchar(1024)")
|
||||||
|
.HasColumnName("path");
|
||||||
|
|
||||||
|
b.Property<int>("ScanIntervalMinutes")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("scan-interval-minutes");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-library-root");
|
||||||
|
|
||||||
|
b.HasIndex("Path")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-library-root-path");
|
||||||
|
|
||||||
|
b.ToTable("managed-library-root", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库根目录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)")
|
||||||
|
.HasColumnName("content-type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<int>("LibraryRootId")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("library-root-id");
|
||||||
|
|
||||||
|
b.Property<string>("RelativePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("varchar(1024)")
|
||||||
|
.HasColumnName("relative-path");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-thumbnail-map");
|
||||||
|
|
||||||
|
b.HasIndex("LibraryRootId")
|
||||||
|
.HasDatabaseName("idx-managed-thumbnail-map-root-id");
|
||||||
|
|
||||||
|
b.HasIndex("RelativePath")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-thumbnail-map-relative-path");
|
||||||
|
|
||||||
|
b.ToTable("managed-thumbnail-map", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件缩略图映射记录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id")
|
||||||
|
.HasComment("用户主键");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("created-at")
|
||||||
|
.HasComment("创建时间");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)")
|
||||||
|
.HasColumnName("email")
|
||||||
|
.HasComment("用户邮箱");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)")
|
||||||
|
.HasColumnName("name")
|
||||||
|
.HasComment("用户名称");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)")
|
||||||
|
.HasColumnName("password-hash")
|
||||||
|
.HasComment("密码哈希值");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("varchar(50)")
|
||||||
|
.HasColumnName("phone-number")
|
||||||
|
.HasComment("电话号码");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("updated-at")
|
||||||
|
.HasComment("更新时间");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-user");
|
||||||
|
|
||||||
|
b.ToTable("user", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("用户实体,演示数据库 CRUD 操作");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id")
|
||||||
|
.HasComment("天气预报主键");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("created-at")
|
||||||
|
.HasComment("创建时间");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("Date")
|
||||||
|
.HasColumnType("date")
|
||||||
|
.HasColumnName("date")
|
||||||
|
.HasComment("预报日期");
|
||||||
|
|
||||||
|
b.Property<string>("Summary")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)")
|
||||||
|
.HasColumnName("summary")
|
||||||
|
.HasComment("天气摘要");
|
||||||
|
|
||||||
|
b.Property<int>("TemperatureC")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("temperature-c")
|
||||||
|
.HasComment("摄氏温度");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("updated-at")
|
||||||
|
.HasComment("更新时间");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-weather-forecast");
|
||||||
|
|
||||||
|
b.ToTable("weather-forecast", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("天气预报数据实体");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("LibraryRootId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedThumbnailMap", "Thumbnail")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("ThumbnailId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("LibraryRoot");
|
||||||
|
|
||||||
|
b.Navigation("Thumbnail");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||||
|
.WithMany("Thumbnails")
|
||||||
|
.HasForeignKey("LibraryRootId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("LibraryRoot");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
|
||||||
|
b.Navigation("Thumbnails");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,101 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using MySql.EntityFrameworkCore.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FileShare_EFCore.Migrations.MySQL
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddThumbnailMap : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "thumbnail-path",
|
||||||
|
table: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "thumbnail-id",
|
||||||
|
table: "managed-file-record",
|
||||||
|
type: "int",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "managed-thumbnail-map",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn),
|
||||||
|
libraryrootid = table.Column<int>(name: "library-root-id", type: "int", nullable: false),
|
||||||
|
relativepath = table.Column<string>(name: "relative-path", type: "varchar(1024)", maxLength: 1024, nullable: false),
|
||||||
|
contenttype = table.Column<string>(name: "content-type", type: "varchar(100)", maxLength: 100, nullable: false),
|
||||||
|
createdat = table.Column<DateTime>(name: "created-at", type: "datetime(6)", nullable: false),
|
||||||
|
updatedat = table.Column<DateTime>(name: "updated-at", type: "datetime(6)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk-managed-thumbnail-map", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_managed-thumbnail-map_managed-library-root_library-root-id",
|
||||||
|
column: x => x.libraryrootid,
|
||||||
|
principalTable: "managed-library-root",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
},
|
||||||
|
comment: "文件缩略图映射记录")
|
||||||
|
.Annotation("MySQL:Charset", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-file-record-thumbnail-id",
|
||||||
|
table: "managed-file-record",
|
||||||
|
column: "thumbnail-id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-thumbnail-map-relative-path",
|
||||||
|
table: "managed-thumbnail-map",
|
||||||
|
column: "relative-path",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-thumbnail-map-root-id",
|
||||||
|
table: "managed-thumbnail-map",
|
||||||
|
column: "library-root-id");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_managed-file-record_managed-thumbnail-map_thumbnail-id",
|
||||||
|
table: "managed-file-record",
|
||||||
|
column: "thumbnail-id",
|
||||||
|
principalTable: "managed-thumbnail-map",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.SetNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_managed-file-record_managed-thumbnail-map_thumbnail-id",
|
||||||
|
table: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "managed-thumbnail-map");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "idx-managed-file-record-thumbnail-id",
|
||||||
|
table: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "thumbnail-id",
|
||||||
|
table: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "thumbnail-path",
|
||||||
|
table: "managed-file-record",
|
||||||
|
type: "varchar(512)",
|
||||||
|
maxLength: 512,
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
using System;
|
using System;
|
||||||
using FileShare_EFCore.Database;
|
using FileShare_EFCore.Database;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -79,6 +79,228 @@ namespace FileShare_EFCore.Migrations.MySQL
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("AbsolutePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("varchar(2048)")
|
||||||
|
.HasColumnName("absolute-path");
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)")
|
||||||
|
.HasColumnName("content-type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<bool>("Exists")
|
||||||
|
.HasColumnType("tinyint(1)")
|
||||||
|
.HasColumnName("exists");
|
||||||
|
|
||||||
|
b.Property<string>("Extension")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("varchar(32)")
|
||||||
|
.HasColumnName("extension");
|
||||||
|
|
||||||
|
b.Property<string>("FileName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(260)
|
||||||
|
.HasColumnType("varchar(260)")
|
||||||
|
.HasColumnName("file-name");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastPlayedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("last-played-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastSeenAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("last-seen-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastWriteTimeUtc")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("last-write-time-utc");
|
||||||
|
|
||||||
|
b.Property<int>("LibraryRootId")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("library-root-id");
|
||||||
|
|
||||||
|
b.Property<string>("MediaType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("varchar(20)")
|
||||||
|
.HasColumnName("media-type");
|
||||||
|
|
||||||
|
b.Property<string>("RelativePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("varchar(1024)")
|
||||||
|
.HasColumnName("relative-path");
|
||||||
|
|
||||||
|
b.Property<long>("SizeBytes")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("size-bytes");
|
||||||
|
|
||||||
|
b.Property<int?>("ThumbnailId")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("thumbnail-id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.Property<double?>("VideoDuration")
|
||||||
|
.HasColumnType("double")
|
||||||
|
.HasColumnName("video-duration");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-file-record");
|
||||||
|
|
||||||
|
b.HasIndex("AbsolutePath")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||||
|
|
||||||
|
b.HasIndex("LastPlayedAt")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||||
|
|
||||||
|
b.HasIndex("LibraryRootId")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||||
|
|
||||||
|
b.HasIndex("ThumbnailId")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-thumbnail-id");
|
||||||
|
|
||||||
|
b.HasIndex("MediaType", "Exists")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||||
|
|
||||||
|
b.ToTable("managed-file-record", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库文件记录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)")
|
||||||
|
.HasColumnName("display-name");
|
||||||
|
|
||||||
|
b.Property<bool>("IsAvailable")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("tinyint(1)")
|
||||||
|
.HasDefaultValue(true)
|
||||||
|
.HasColumnName("is-available");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEnabled")
|
||||||
|
.HasColumnType("tinyint(1)")
|
||||||
|
.HasColumnName("is-enabled");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanCompletedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("last-scan-completed-at");
|
||||||
|
|
||||||
|
b.Property<string>("LastScanError")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("varchar(2000)")
|
||||||
|
.HasColumnName("last-scan-error");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanStartedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("last-scan-started-at");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("varchar(1024)")
|
||||||
|
.HasColumnName("path");
|
||||||
|
|
||||||
|
b.Property<int>("ScanIntervalMinutes")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("scan-interval-minutes");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-library-root");
|
||||||
|
|
||||||
|
b.HasIndex("Path")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-library-root-path");
|
||||||
|
|
||||||
|
b.ToTable("managed-library-root", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库根目录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)")
|
||||||
|
.HasColumnName("content-type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<int>("LibraryRootId")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("library-root-id");
|
||||||
|
|
||||||
|
b.Property<string>("RelativePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("varchar(1024)")
|
||||||
|
.HasColumnName("relative-path");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime(6)")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-thumbnail-map");
|
||||||
|
|
||||||
|
b.HasIndex("LibraryRootId")
|
||||||
|
.HasDatabaseName("idx-managed-thumbnail-map-root-id");
|
||||||
|
|
||||||
|
b.HasIndex("RelativePath")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-thumbnail-map-relative-path");
|
||||||
|
|
||||||
|
b.ToTable("managed-thumbnail-map", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件缩略图映射记录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@ -172,6 +394,47 @@ namespace FileShare_EFCore.Migrations.MySQL
|
|||||||
t.HasComment("天气预报数据实体");
|
t.HasComment("天气预报数据实体");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("LibraryRootId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedThumbnailMap", "Thumbnail")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("ThumbnailId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("LibraryRoot");
|
||||||
|
|
||||||
|
b.Navigation("Thumbnail");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||||
|
.WithMany("Thumbnails")
|
||||||
|
.HasForeignKey("LibraryRootId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("LibraryRoot");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
|
||||||
|
b.Navigation("Thumbnails");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
383
FileShare-EFCore/Migrations/PostgreSQL/20260522082843_AutoMigration_20260522162758.Designer.cs
generated
Normal file
383
FileShare-EFCore/Migrations/PostgreSQL/20260522082843_AutoMigration_20260522162758.Designer.cs
generated
Normal file
@ -0,0 +1,383 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using FileShare_EFCore.Database;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FileShare_EFCore.Migrations.PostgreSQL
|
||||||
|
{
|
||||||
|
[DbContext(typeof(PostgreSqlAppDataContext))]
|
||||||
|
[Migration("20260522082843_AutoMigration_20260522162758")]
|
||||||
|
partial class AutoMigration_20260522162758
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.7")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<string>("Device")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)")
|
||||||
|
.HasColumnName("device");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExpiresAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("expires-at");
|
||||||
|
|
||||||
|
b.Property<string>("IpAddress")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)")
|
||||||
|
.HasColumnName("ip-address");
|
||||||
|
|
||||||
|
b.Property<string>("ReplacedByTokenHash")
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)")
|
||||||
|
.HasColumnName("replaced-by-token-hash");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("RevokedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("revoked-at");
|
||||||
|
|
||||||
|
b.Property<string>("TokenHash")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)")
|
||||||
|
.HasColumnName("token-hash");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("user-id");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-api-refresh-token");
|
||||||
|
|
||||||
|
b.HasIndex("TokenHash")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-api-refresh-token-hash");
|
||||||
|
|
||||||
|
b.HasIndex("UserId")
|
||||||
|
.HasDatabaseName("idx-api-refresh-token-user-id");
|
||||||
|
|
||||||
|
b.ToTable("api-refresh-token", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("API refresh token");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("AbsolutePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)")
|
||||||
|
.HasColumnName("absolute-path");
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("character varying(100)")
|
||||||
|
.HasColumnName("content-type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<bool>("Exists")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("exists");
|
||||||
|
|
||||||
|
b.Property<string>("Extension")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)")
|
||||||
|
.HasColumnName("extension");
|
||||||
|
|
||||||
|
b.Property<string>("FileName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(260)
|
||||||
|
.HasColumnType("character varying(260)")
|
||||||
|
.HasColumnName("file-name");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastPlayedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("last-played-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastSeenAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("last-seen-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastWriteTimeUtc")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("last-write-time-utc");
|
||||||
|
|
||||||
|
b.Property<int>("LibraryRootId")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("library-root-id");
|
||||||
|
|
||||||
|
b.Property<string>("MediaType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("character varying(20)")
|
||||||
|
.HasColumnName("media-type");
|
||||||
|
|
||||||
|
b.Property<string>("RelativePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("relative-path");
|
||||||
|
|
||||||
|
b.Property<long>("SizeBytes")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("size-bytes");
|
||||||
|
|
||||||
|
b.Property<string>("ThumbnailPath")
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)")
|
||||||
|
.HasColumnName("thumbnail-path");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.Property<double?>("VideoDuration")
|
||||||
|
.HasColumnType("double precision")
|
||||||
|
.HasColumnName("video-duration");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-file-record");
|
||||||
|
|
||||||
|
b.HasIndex("AbsolutePath")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||||
|
|
||||||
|
b.HasIndex("LastPlayedAt")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||||
|
|
||||||
|
b.HasIndex("LibraryRootId")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||||
|
|
||||||
|
b.HasIndex("MediaType", "Exists")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||||
|
|
||||||
|
b.ToTable("managed-file-record", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库文件记录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)")
|
||||||
|
.HasColumnName("display-name");
|
||||||
|
|
||||||
|
b.Property<bool>("IsAvailable")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true)
|
||||||
|
.HasColumnName("is-available");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEnabled")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("is-enabled");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanCompletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("last-scan-completed-at");
|
||||||
|
|
||||||
|
b.Property<string>("LastScanError")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("character varying(2000)")
|
||||||
|
.HasColumnName("last-scan-error");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanStartedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("last-scan-started-at");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("path");
|
||||||
|
|
||||||
|
b.Property<int>("ScanIntervalMinutes")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("scan-interval-minutes");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-library-root");
|
||||||
|
|
||||||
|
b.HasIndex("Path")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-library-root-path");
|
||||||
|
|
||||||
|
b.ToTable("managed-library-root", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库根目录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id")
|
||||||
|
.HasComment("用户主键");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created-at")
|
||||||
|
.HasComment("创建时间");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)")
|
||||||
|
.HasColumnName("email")
|
||||||
|
.HasComment("用户邮箱");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("character varying(100)")
|
||||||
|
.HasColumnName("name")
|
||||||
|
.HasComment("用户名称");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)")
|
||||||
|
.HasColumnName("password-hash")
|
||||||
|
.HasComment("密码哈希值");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("character varying(50)")
|
||||||
|
.HasColumnName("phone-number")
|
||||||
|
.HasComment("电话号码");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated-at")
|
||||||
|
.HasComment("更新时间");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-user");
|
||||||
|
|
||||||
|
b.ToTable("user", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("用户实体,演示数据库 CRUD 操作");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id")
|
||||||
|
.HasComment("天气预报主键");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created-at")
|
||||||
|
.HasComment("创建时间");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("Date")
|
||||||
|
.HasColumnType("date")
|
||||||
|
.HasColumnName("date")
|
||||||
|
.HasComment("预报日期");
|
||||||
|
|
||||||
|
b.Property<string>("Summary")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)")
|
||||||
|
.HasColumnName("summary")
|
||||||
|
.HasComment("天气摘要");
|
||||||
|
|
||||||
|
b.Property<int>("TemperatureC")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("temperature-c")
|
||||||
|
.HasComment("摄氏温度");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated-at")
|
||||||
|
.HasComment("更新时间");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-weather-forecast");
|
||||||
|
|
||||||
|
b.ToTable("weather-forecast", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("天气预报数据实体");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("LibraryRootId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("LibraryRoot");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,111 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FileShare_EFCore.Migrations.PostgreSQL
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AutoMigration_20260522162758 : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "managed-library-root",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
path = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||||
|
displayname = table.Column<string>(name: "display-name", type: "character varying(200)", maxLength: 200, nullable: false),
|
||||||
|
isenabled = table.Column<bool>(name: "is-enabled", type: "boolean", nullable: false),
|
||||||
|
isavailable = table.Column<bool>(name: "is-available", type: "boolean", nullable: false, defaultValue: true),
|
||||||
|
scanintervalminutes = table.Column<int>(name: "scan-interval-minutes", type: "integer", nullable: false),
|
||||||
|
lastscanstartedat = table.Column<DateTime>(name: "last-scan-started-at", type: "timestamp with time zone", nullable: true),
|
||||||
|
lastscancompletedat = table.Column<DateTime>(name: "last-scan-completed-at", type: "timestamp with time zone", nullable: true),
|
||||||
|
lastscanerror = table.Column<string>(name: "last-scan-error", type: "character varying(2000)", maxLength: 2000, nullable: true),
|
||||||
|
createdat = table.Column<DateTime>(name: "created-at", type: "timestamp with time zone", nullable: false),
|
||||||
|
updatedat = table.Column<DateTime>(name: "updated-at", type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk-managed-library-root", x => x.id);
|
||||||
|
},
|
||||||
|
comment: "文件库根目录");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "managed-file-record",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
libraryrootid = table.Column<int>(name: "library-root-id", type: "integer", nullable: false),
|
||||||
|
filename = table.Column<string>(name: "file-name", type: "character varying(260)", maxLength: 260, nullable: false),
|
||||||
|
relativepath = table.Column<string>(name: "relative-path", type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||||
|
absolutepath = table.Column<string>(name: "absolute-path", type: "character varying(2048)", maxLength: 2048, nullable: false),
|
||||||
|
extension = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||||
|
sizebytes = table.Column<long>(name: "size-bytes", type: "bigint", nullable: false),
|
||||||
|
lastwritetimeutc = table.Column<DateTime>(name: "last-write-time-utc", type: "timestamp with time zone", nullable: false),
|
||||||
|
mediatype = table.Column<string>(name: "media-type", type: "character varying(20)", maxLength: 20, nullable: false),
|
||||||
|
contenttype = table.Column<string>(name: "content-type", type: "character varying(100)", maxLength: 100, nullable: false),
|
||||||
|
exists = table.Column<bool>(type: "boolean", nullable: false),
|
||||||
|
lastseenat = table.Column<DateTime>(name: "last-seen-at", type: "timestamp with time zone", nullable: false),
|
||||||
|
thumbnailpath = table.Column<string>(name: "thumbnail-path", type: "character varying(512)", maxLength: 512, nullable: true),
|
||||||
|
videoduration = table.Column<double>(name: "video-duration", type: "double precision", nullable: true),
|
||||||
|
lastplayedat = table.Column<DateTime>(name: "last-played-at", type: "timestamp with time zone", nullable: true),
|
||||||
|
createdat = table.Column<DateTime>(name: "created-at", type: "timestamp with time zone", nullable: false),
|
||||||
|
updatedat = table.Column<DateTime>(name: "updated-at", type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk-managed-file-record", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_managed-file-record_managed-library-root_library-root-id",
|
||||||
|
column: x => x.libraryrootid,
|
||||||
|
principalTable: "managed-library-root",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
},
|
||||||
|
comment: "文件库文件记录");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-file-record-absolute-path",
|
||||||
|
table: "managed-file-record",
|
||||||
|
column: "absolute-path",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-file-record-last-played-at",
|
||||||
|
table: "managed-file-record",
|
||||||
|
column: "last-played-at");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-file-record-media-type-exists",
|
||||||
|
table: "managed-file-record",
|
||||||
|
columns: new[] { "media-type", "exists" });
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-file-record-root-id",
|
||||||
|
table: "managed-file-record",
|
||||||
|
column: "library-root-id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-library-root-path",
|
||||||
|
table: "managed-library-root",
|
||||||
|
column: "path",
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "managed-library-root");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
459
FileShare-EFCore/Migrations/PostgreSQL/20260522084325_AddThumbnailMap.Designer.cs
generated
Normal file
459
FileShare-EFCore/Migrations/PostgreSQL/20260522084325_AddThumbnailMap.Designer.cs
generated
Normal file
@ -0,0 +1,459 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using FileShare_EFCore.Database;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FileShare_EFCore.Migrations.PostgreSQL
|
||||||
|
{
|
||||||
|
[DbContext(typeof(PostgreSqlAppDataContext))]
|
||||||
|
[Migration("20260522084325_AddThumbnailMap")]
|
||||||
|
partial class AddThumbnailMap
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.7")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<string>("Device")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)")
|
||||||
|
.HasColumnName("device");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExpiresAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("expires-at");
|
||||||
|
|
||||||
|
b.Property<string>("IpAddress")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)")
|
||||||
|
.HasColumnName("ip-address");
|
||||||
|
|
||||||
|
b.Property<string>("ReplacedByTokenHash")
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)")
|
||||||
|
.HasColumnName("replaced-by-token-hash");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("RevokedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("revoked-at");
|
||||||
|
|
||||||
|
b.Property<string>("TokenHash")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)")
|
||||||
|
.HasColumnName("token-hash");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("user-id");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-api-refresh-token");
|
||||||
|
|
||||||
|
b.HasIndex("TokenHash")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-api-refresh-token-hash");
|
||||||
|
|
||||||
|
b.HasIndex("UserId")
|
||||||
|
.HasDatabaseName("idx-api-refresh-token-user-id");
|
||||||
|
|
||||||
|
b.ToTable("api-refresh-token", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("API refresh token");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("AbsolutePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)")
|
||||||
|
.HasColumnName("absolute-path");
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("character varying(100)")
|
||||||
|
.HasColumnName("content-type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<bool>("Exists")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("exists");
|
||||||
|
|
||||||
|
b.Property<string>("Extension")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)")
|
||||||
|
.HasColumnName("extension");
|
||||||
|
|
||||||
|
b.Property<string>("FileName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(260)
|
||||||
|
.HasColumnType("character varying(260)")
|
||||||
|
.HasColumnName("file-name");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastPlayedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("last-played-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastSeenAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("last-seen-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastWriteTimeUtc")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("last-write-time-utc");
|
||||||
|
|
||||||
|
b.Property<int>("LibraryRootId")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("library-root-id");
|
||||||
|
|
||||||
|
b.Property<string>("MediaType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("character varying(20)")
|
||||||
|
.HasColumnName("media-type");
|
||||||
|
|
||||||
|
b.Property<string>("RelativePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("relative-path");
|
||||||
|
|
||||||
|
b.Property<long>("SizeBytes")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("size-bytes");
|
||||||
|
|
||||||
|
b.Property<int?>("ThumbnailId")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("thumbnail-id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.Property<double?>("VideoDuration")
|
||||||
|
.HasColumnType("double precision")
|
||||||
|
.HasColumnName("video-duration");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-file-record");
|
||||||
|
|
||||||
|
b.HasIndex("AbsolutePath")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||||
|
|
||||||
|
b.HasIndex("LastPlayedAt")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||||
|
|
||||||
|
b.HasIndex("LibraryRootId")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||||
|
|
||||||
|
b.HasIndex("ThumbnailId")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-thumbnail-id");
|
||||||
|
|
||||||
|
b.HasIndex("MediaType", "Exists")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||||
|
|
||||||
|
b.ToTable("managed-file-record", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库文件记录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)")
|
||||||
|
.HasColumnName("display-name");
|
||||||
|
|
||||||
|
b.Property<bool>("IsAvailable")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true)
|
||||||
|
.HasColumnName("is-available");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEnabled")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("is-enabled");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanCompletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("last-scan-completed-at");
|
||||||
|
|
||||||
|
b.Property<string>("LastScanError")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("character varying(2000)")
|
||||||
|
.HasColumnName("last-scan-error");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanStartedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("last-scan-started-at");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("path");
|
||||||
|
|
||||||
|
b.Property<int>("ScanIntervalMinutes")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("scan-interval-minutes");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-library-root");
|
||||||
|
|
||||||
|
b.HasIndex("Path")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-library-root-path");
|
||||||
|
|
||||||
|
b.ToTable("managed-library-root", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库根目录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("character varying(100)")
|
||||||
|
.HasColumnName("content-type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<int>("LibraryRootId")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("library-root-id");
|
||||||
|
|
||||||
|
b.Property<string>("RelativePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("relative-path");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-thumbnail-map");
|
||||||
|
|
||||||
|
b.HasIndex("LibraryRootId")
|
||||||
|
.HasDatabaseName("idx-managed-thumbnail-map-root-id");
|
||||||
|
|
||||||
|
b.HasIndex("RelativePath")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-thumbnail-map-relative-path");
|
||||||
|
|
||||||
|
b.ToTable("managed-thumbnail-map", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件缩略图映射记录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id")
|
||||||
|
.HasComment("用户主键");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created-at")
|
||||||
|
.HasComment("创建时间");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)")
|
||||||
|
.HasColumnName("email")
|
||||||
|
.HasComment("用户邮箱");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("character varying(100)")
|
||||||
|
.HasColumnName("name")
|
||||||
|
.HasComment("用户名称");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)")
|
||||||
|
.HasColumnName("password-hash")
|
||||||
|
.HasComment("密码哈希值");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("character varying(50)")
|
||||||
|
.HasColumnName("phone-number")
|
||||||
|
.HasComment("电话号码");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated-at")
|
||||||
|
.HasComment("更新时间");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-user");
|
||||||
|
|
||||||
|
b.ToTable("user", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("用户实体,演示数据库 CRUD 操作");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id")
|
||||||
|
.HasComment("天气预报主键");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created-at")
|
||||||
|
.HasComment("创建时间");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("Date")
|
||||||
|
.HasColumnType("date")
|
||||||
|
.HasColumnName("date")
|
||||||
|
.HasComment("预报日期");
|
||||||
|
|
||||||
|
b.Property<string>("Summary")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)")
|
||||||
|
.HasColumnName("summary")
|
||||||
|
.HasComment("天气摘要");
|
||||||
|
|
||||||
|
b.Property<int>("TemperatureC")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("temperature-c")
|
||||||
|
.HasComment("摄氏温度");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated-at")
|
||||||
|
.HasComment("更新时间");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-weather-forecast");
|
||||||
|
|
||||||
|
b.ToTable("weather-forecast", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("天气预报数据实体");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("LibraryRootId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedThumbnailMap", "Thumbnail")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("ThumbnailId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("LibraryRoot");
|
||||||
|
|
||||||
|
b.Navigation("Thumbnail");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||||
|
.WithMany("Thumbnails")
|
||||||
|
.HasForeignKey("LibraryRootId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("LibraryRoot");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
|
||||||
|
b.Navigation("Thumbnails");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,100 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FileShare_EFCore.Migrations.PostgreSQL
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddThumbnailMap : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "thumbnail-path",
|
||||||
|
table: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "thumbnail-id",
|
||||||
|
table: "managed-file-record",
|
||||||
|
type: "integer",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "managed-thumbnail-map",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
libraryrootid = table.Column<int>(name: "library-root-id", type: "integer", nullable: false),
|
||||||
|
relativepath = table.Column<string>(name: "relative-path", type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||||
|
contenttype = table.Column<string>(name: "content-type", type: "character varying(100)", maxLength: 100, nullable: false),
|
||||||
|
createdat = table.Column<DateTime>(name: "created-at", type: "timestamp with time zone", nullable: false),
|
||||||
|
updatedat = table.Column<DateTime>(name: "updated-at", type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk-managed-thumbnail-map", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_managed-thumbnail-map_managed-library-root_library-root-id",
|
||||||
|
column: x => x.libraryrootid,
|
||||||
|
principalTable: "managed-library-root",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
},
|
||||||
|
comment: "文件缩略图映射记录");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-file-record-thumbnail-id",
|
||||||
|
table: "managed-file-record",
|
||||||
|
column: "thumbnail-id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-thumbnail-map-relative-path",
|
||||||
|
table: "managed-thumbnail-map",
|
||||||
|
column: "relative-path",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-thumbnail-map-root-id",
|
||||||
|
table: "managed-thumbnail-map",
|
||||||
|
column: "library-root-id");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_managed-file-record_managed-thumbnail-map_thumbnail-id",
|
||||||
|
table: "managed-file-record",
|
||||||
|
column: "thumbnail-id",
|
||||||
|
principalTable: "managed-thumbnail-map",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.SetNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_managed-file-record_managed-thumbnail-map_thumbnail-id",
|
||||||
|
table: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "managed-thumbnail-map");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "idx-managed-file-record-thumbnail-id",
|
||||||
|
table: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "thumbnail-id",
|
||||||
|
table: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "thumbnail-path",
|
||||||
|
table: "managed-file-record",
|
||||||
|
type: "character varying(512)",
|
||||||
|
maxLength: 512,
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
using System;
|
using System;
|
||||||
using FileShare_EFCore.Database;
|
using FileShare_EFCore.Database;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -84,6 +84,234 @@ namespace FileShare_EFCore.Migrations.PostgreSQL
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("AbsolutePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)")
|
||||||
|
.HasColumnName("absolute-path");
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("character varying(100)")
|
||||||
|
.HasColumnName("content-type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<bool>("Exists")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("exists");
|
||||||
|
|
||||||
|
b.Property<string>("Extension")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)")
|
||||||
|
.HasColumnName("extension");
|
||||||
|
|
||||||
|
b.Property<string>("FileName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(260)
|
||||||
|
.HasColumnType("character varying(260)")
|
||||||
|
.HasColumnName("file-name");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastPlayedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("last-played-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastSeenAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("last-seen-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastWriteTimeUtc")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("last-write-time-utc");
|
||||||
|
|
||||||
|
b.Property<int>("LibraryRootId")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("library-root-id");
|
||||||
|
|
||||||
|
b.Property<string>("MediaType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("character varying(20)")
|
||||||
|
.HasColumnName("media-type");
|
||||||
|
|
||||||
|
b.Property<string>("RelativePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("relative-path");
|
||||||
|
|
||||||
|
b.Property<long>("SizeBytes")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("size-bytes");
|
||||||
|
|
||||||
|
b.Property<int?>("ThumbnailId")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("thumbnail-id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.Property<double?>("VideoDuration")
|
||||||
|
.HasColumnType("double precision")
|
||||||
|
.HasColumnName("video-duration");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-file-record");
|
||||||
|
|
||||||
|
b.HasIndex("AbsolutePath")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||||
|
|
||||||
|
b.HasIndex("LastPlayedAt")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||||
|
|
||||||
|
b.HasIndex("LibraryRootId")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||||
|
|
||||||
|
b.HasIndex("ThumbnailId")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-thumbnail-id");
|
||||||
|
|
||||||
|
b.HasIndex("MediaType", "Exists")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||||
|
|
||||||
|
b.ToTable("managed-file-record", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库文件记录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)")
|
||||||
|
.HasColumnName("display-name");
|
||||||
|
|
||||||
|
b.Property<bool>("IsAvailable")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true)
|
||||||
|
.HasColumnName("is-available");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEnabled")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("is-enabled");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanCompletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("last-scan-completed-at");
|
||||||
|
|
||||||
|
b.Property<string>("LastScanError")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("character varying(2000)")
|
||||||
|
.HasColumnName("last-scan-error");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanStartedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("last-scan-started-at");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("path");
|
||||||
|
|
||||||
|
b.Property<int>("ScanIntervalMinutes")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("scan-interval-minutes");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-library-root");
|
||||||
|
|
||||||
|
b.HasIndex("Path")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-library-root-path");
|
||||||
|
|
||||||
|
b.ToTable("managed-library-root", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库根目录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("character varying(100)")
|
||||||
|
.HasColumnName("content-type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<int>("LibraryRootId")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("library-root-id");
|
||||||
|
|
||||||
|
b.Property<string>("RelativePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("relative-path");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-thumbnail-map");
|
||||||
|
|
||||||
|
b.HasIndex("LibraryRootId")
|
||||||
|
.HasDatabaseName("idx-managed-thumbnail-map-root-id");
|
||||||
|
|
||||||
|
b.HasIndex("RelativePath")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-thumbnail-map-relative-path");
|
||||||
|
|
||||||
|
b.ToTable("managed-thumbnail-map", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件缩略图映射记录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@ -181,6 +409,47 @@ namespace FileShare_EFCore.Migrations.PostgreSQL
|
|||||||
t.HasComment("天气预报数据实体");
|
t.HasComment("天气预报数据实体");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("LibraryRootId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedThumbnailMap", "Thumbnail")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("ThumbnailId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("LibraryRoot");
|
||||||
|
|
||||||
|
b.Navigation("Thumbnail");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||||
|
.WithMany("Thumbnails")
|
||||||
|
.HasForeignKey("LibraryRootId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("LibraryRoot");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
|
||||||
|
b.Navigation("Thumbnails");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
368
FileShare-EFCore/Migrations/SQLite/20260522082814_AutoMigration_20260522162758.Designer.cs
generated
Normal file
368
FileShare-EFCore/Migrations/SQLite/20260522082814_AutoMigration_20260522162758.Designer.cs
generated
Normal file
@ -0,0 +1,368 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using FileShare_EFCore.Database;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FileShare_EFCore.Migrations.SQLite
|
||||||
|
{
|
||||||
|
[DbContext(typeof(SqliteAppDataContext))]
|
||||||
|
[Migration("20260522082814_AutoMigration_20260522162758")]
|
||||||
|
partial class AutoMigration_20260522162758
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "10.0.7");
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<string>("Device")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("device");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExpiresAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("expires-at");
|
||||||
|
|
||||||
|
b.Property<string>("IpAddress")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("ip-address");
|
||||||
|
|
||||||
|
b.Property<string>("ReplacedByTokenHash")
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("replaced-by-token-hash");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("RevokedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("revoked-at");
|
||||||
|
|
||||||
|
b.Property<string>("TokenHash")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("token-hash");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("user-id");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-api-refresh-token");
|
||||||
|
|
||||||
|
b.HasIndex("TokenHash")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-api-refresh-token-hash");
|
||||||
|
|
||||||
|
b.HasIndex("UserId")
|
||||||
|
.HasDatabaseName("idx-api-refresh-token-user-id");
|
||||||
|
|
||||||
|
b.ToTable("api-refresh-token", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("API refresh token");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("AbsolutePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("absolute-path");
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("content-type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<bool>("Exists")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("exists");
|
||||||
|
|
||||||
|
b.Property<string>("Extension")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("extension");
|
||||||
|
|
||||||
|
b.Property<string>("FileName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(260)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("file-name");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastPlayedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("last-played-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastSeenAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("last-seen-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastWriteTimeUtc")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("last-write-time-utc");
|
||||||
|
|
||||||
|
b.Property<int>("LibraryRootId")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("library-root-id");
|
||||||
|
|
||||||
|
b.Property<string>("MediaType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("media-type");
|
||||||
|
|
||||||
|
b.Property<string>("RelativePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("relative-path");
|
||||||
|
|
||||||
|
b.Property<long>("SizeBytes")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("size-bytes");
|
||||||
|
|
||||||
|
b.Property<string>("ThumbnailPath")
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("thumbnail-path");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.Property<double?>("VideoDuration")
|
||||||
|
.HasColumnType("REAL")
|
||||||
|
.HasColumnName("video-duration");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-file-record");
|
||||||
|
|
||||||
|
b.HasIndex("AbsolutePath")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||||
|
|
||||||
|
b.HasIndex("LastPlayedAt")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||||
|
|
||||||
|
b.HasIndex("LibraryRootId")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||||
|
|
||||||
|
b.HasIndex("MediaType", "Exists")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||||
|
|
||||||
|
b.ToTable("managed-file-record", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库文件记录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("display-name");
|
||||||
|
|
||||||
|
b.Property<bool>("IsAvailable")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(true)
|
||||||
|
.HasColumnName("is-available");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEnabled")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("is-enabled");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanCompletedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("last-scan-completed-at");
|
||||||
|
|
||||||
|
b.Property<string>("LastScanError")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("last-scan-error");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanStartedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("last-scan-started-at");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("path");
|
||||||
|
|
||||||
|
b.Property<int>("ScanIntervalMinutes")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("scan-interval-minutes");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-library-root");
|
||||||
|
|
||||||
|
b.HasIndex("Path")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-library-root-path");
|
||||||
|
|
||||||
|
b.ToTable("managed-library-root", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库根目录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id")
|
||||||
|
.HasComment("用户主键");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created-at")
|
||||||
|
.HasComment("创建时间");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("email")
|
||||||
|
.HasComment("用户邮箱");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("name")
|
||||||
|
.HasComment("用户名称");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("password-hash")
|
||||||
|
.HasComment("密码哈希值");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("phone-number")
|
||||||
|
.HasComment("电话号码");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("updated-at")
|
||||||
|
.HasComment("更新时间");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-user");
|
||||||
|
|
||||||
|
b.ToTable("user", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("用户实体,演示数据库 CRUD 操作");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id")
|
||||||
|
.HasComment("天气预报主键");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created-at")
|
||||||
|
.HasComment("创建时间");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("Date")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("date")
|
||||||
|
.HasComment("预报日期");
|
||||||
|
|
||||||
|
b.Property<string>("Summary")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("summary")
|
||||||
|
.HasComment("天气摘要");
|
||||||
|
|
||||||
|
b.Property<int>("TemperatureC")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("temperature-c")
|
||||||
|
.HasComment("摄氏温度");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("updated-at")
|
||||||
|
.HasComment("更新时间");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-weather-forecast");
|
||||||
|
|
||||||
|
b.ToTable("weather-forecast", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("天气预报数据实体");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("LibraryRootId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("LibraryRoot");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FileShare_EFCore.Migrations.SQLite
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AutoMigration_20260522162758 : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<DateTime>(
|
||||||
|
name: "last-played-at",
|
||||||
|
table: "managed-file-record",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "thumbnail-path",
|
||||||
|
table: "managed-file-record",
|
||||||
|
type: "TEXT",
|
||||||
|
maxLength: 512,
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<double>(
|
||||||
|
name: "video-duration",
|
||||||
|
table: "managed-file-record",
|
||||||
|
type: "REAL",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-file-record-last-played-at",
|
||||||
|
table: "managed-file-record",
|
||||||
|
column: "last-played-at");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "idx-managed-file-record-last-played-at",
|
||||||
|
table: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "last-played-at",
|
||||||
|
table: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "thumbnail-path",
|
||||||
|
table: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "video-duration",
|
||||||
|
table: "managed-file-record");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
442
FileShare-EFCore/Migrations/SQLite/20260522084259_AddThumbnailMap.Designer.cs
generated
Normal file
442
FileShare-EFCore/Migrations/SQLite/20260522084259_AddThumbnailMap.Designer.cs
generated
Normal file
@ -0,0 +1,442 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using FileShare_EFCore.Database;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FileShare_EFCore.Migrations.SQLite
|
||||||
|
{
|
||||||
|
[DbContext(typeof(SqliteAppDataContext))]
|
||||||
|
[Migration("20260522084259_AddThumbnailMap")]
|
||||||
|
partial class AddThumbnailMap
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "10.0.7");
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<string>("Device")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("device");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExpiresAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("expires-at");
|
||||||
|
|
||||||
|
b.Property<string>("IpAddress")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("ip-address");
|
||||||
|
|
||||||
|
b.Property<string>("ReplacedByTokenHash")
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("replaced-by-token-hash");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("RevokedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("revoked-at");
|
||||||
|
|
||||||
|
b.Property<string>("TokenHash")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("token-hash");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("user-id");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-api-refresh-token");
|
||||||
|
|
||||||
|
b.HasIndex("TokenHash")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-api-refresh-token-hash");
|
||||||
|
|
||||||
|
b.HasIndex("UserId")
|
||||||
|
.HasDatabaseName("idx-api-refresh-token-user-id");
|
||||||
|
|
||||||
|
b.ToTable("api-refresh-token", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("API refresh token");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("AbsolutePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("absolute-path");
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("content-type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<bool>("Exists")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("exists");
|
||||||
|
|
||||||
|
b.Property<string>("Extension")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("extension");
|
||||||
|
|
||||||
|
b.Property<string>("FileName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(260)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("file-name");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastPlayedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("last-played-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastSeenAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("last-seen-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastWriteTimeUtc")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("last-write-time-utc");
|
||||||
|
|
||||||
|
b.Property<int>("LibraryRootId")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("library-root-id");
|
||||||
|
|
||||||
|
b.Property<string>("MediaType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("media-type");
|
||||||
|
|
||||||
|
b.Property<string>("RelativePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("relative-path");
|
||||||
|
|
||||||
|
b.Property<long>("SizeBytes")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("size-bytes");
|
||||||
|
|
||||||
|
b.Property<int?>("ThumbnailId")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("thumbnail-id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.Property<double?>("VideoDuration")
|
||||||
|
.HasColumnType("REAL")
|
||||||
|
.HasColumnName("video-duration");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-file-record");
|
||||||
|
|
||||||
|
b.HasIndex("AbsolutePath")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||||
|
|
||||||
|
b.HasIndex("LastPlayedAt")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||||
|
|
||||||
|
b.HasIndex("LibraryRootId")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||||
|
|
||||||
|
b.HasIndex("ThumbnailId")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-thumbnail-id");
|
||||||
|
|
||||||
|
b.HasIndex("MediaType", "Exists")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||||
|
|
||||||
|
b.ToTable("managed-file-record", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库文件记录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("display-name");
|
||||||
|
|
||||||
|
b.Property<bool>("IsAvailable")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(true)
|
||||||
|
.HasColumnName("is-available");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEnabled")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("is-enabled");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanCompletedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("last-scan-completed-at");
|
||||||
|
|
||||||
|
b.Property<string>("LastScanError")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("last-scan-error");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanStartedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("last-scan-started-at");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("path");
|
||||||
|
|
||||||
|
b.Property<int>("ScanIntervalMinutes")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("scan-interval-minutes");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-library-root");
|
||||||
|
|
||||||
|
b.HasIndex("Path")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-library-root-path");
|
||||||
|
|
||||||
|
b.ToTable("managed-library-root", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库根目录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("content-type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<int>("LibraryRootId")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("library-root-id");
|
||||||
|
|
||||||
|
b.Property<string>("RelativePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("relative-path");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-thumbnail-map");
|
||||||
|
|
||||||
|
b.HasIndex("LibraryRootId")
|
||||||
|
.HasDatabaseName("idx-managed-thumbnail-map-root-id");
|
||||||
|
|
||||||
|
b.HasIndex("RelativePath")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-thumbnail-map-relative-path");
|
||||||
|
|
||||||
|
b.ToTable("managed-thumbnail-map", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件缩略图映射记录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id")
|
||||||
|
.HasComment("用户主键");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created-at")
|
||||||
|
.HasComment("创建时间");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("email")
|
||||||
|
.HasComment("用户邮箱");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("name")
|
||||||
|
.HasComment("用户名称");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("password-hash")
|
||||||
|
.HasComment("密码哈希值");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("phone-number")
|
||||||
|
.HasComment("电话号码");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("updated-at")
|
||||||
|
.HasComment("更新时间");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-user");
|
||||||
|
|
||||||
|
b.ToTable("user", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("用户实体,演示数据库 CRUD 操作");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id")
|
||||||
|
.HasComment("天气预报主键");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created-at")
|
||||||
|
.HasComment("创建时间");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("Date")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("date")
|
||||||
|
.HasComment("预报日期");
|
||||||
|
|
||||||
|
b.Property<string>("Summary")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("summary")
|
||||||
|
.HasComment("天气摘要");
|
||||||
|
|
||||||
|
b.Property<int>("TemperatureC")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("temperature-c")
|
||||||
|
.HasComment("摄氏温度");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("updated-at")
|
||||||
|
.HasComment("更新时间");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-weather-forecast");
|
||||||
|
|
||||||
|
b.ToTable("weather-forecast", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("天气预报数据实体");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("LibraryRootId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedThumbnailMap", "Thumbnail")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("ThumbnailId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("LibraryRoot");
|
||||||
|
|
||||||
|
b.Navigation("Thumbnail");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||||
|
.WithMany("Thumbnails")
|
||||||
|
.HasForeignKey("LibraryRootId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("LibraryRoot");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
|
||||||
|
b.Navigation("Thumbnails");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,99 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FileShare_EFCore.Migrations.SQLite
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddThumbnailMap : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "thumbnail-path",
|
||||||
|
table: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "thumbnail-id",
|
||||||
|
table: "managed-file-record",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "managed-thumbnail-map",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
libraryrootid = table.Column<int>(name: "library-root-id", type: "INTEGER", nullable: false),
|
||||||
|
relativepath = table.Column<string>(name: "relative-path", type: "TEXT", maxLength: 1024, nullable: false),
|
||||||
|
contenttype = table.Column<string>(name: "content-type", type: "TEXT", maxLength: 100, nullable: false),
|
||||||
|
createdat = table.Column<DateTime>(name: "created-at", type: "TEXT", nullable: false),
|
||||||
|
updatedat = table.Column<DateTime>(name: "updated-at", type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk-managed-thumbnail-map", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_managed-thumbnail-map_managed-library-root_library-root-id",
|
||||||
|
column: x => x.libraryrootid,
|
||||||
|
principalTable: "managed-library-root",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
},
|
||||||
|
comment: "文件缩略图映射记录");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-file-record-thumbnail-id",
|
||||||
|
table: "managed-file-record",
|
||||||
|
column: "thumbnail-id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-thumbnail-map-relative-path",
|
||||||
|
table: "managed-thumbnail-map",
|
||||||
|
column: "relative-path",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-thumbnail-map-root-id",
|
||||||
|
table: "managed-thumbnail-map",
|
||||||
|
column: "library-root-id");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_managed-file-record_managed-thumbnail-map_thumbnail-id",
|
||||||
|
table: "managed-file-record",
|
||||||
|
column: "thumbnail-id",
|
||||||
|
principalTable: "managed-thumbnail-map",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.SetNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_managed-file-record_managed-thumbnail-map_thumbnail-id",
|
||||||
|
table: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "managed-thumbnail-map");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "idx-managed-file-record-thumbnail-id",
|
||||||
|
table: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "thumbnail-id",
|
||||||
|
table: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "thumbnail-path",
|
||||||
|
table: "managed-file-record",
|
||||||
|
type: "TEXT",
|
||||||
|
maxLength: 512,
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
using System;
|
using System;
|
||||||
using FileShare_EFCore.Database;
|
using FileShare_EFCore.Database;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -116,6 +116,10 @@ namespace FileShare_EFCore.Migrations.SQLite
|
|||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("file-name");
|
.HasColumnName("file-name");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastPlayedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("last-played-at");
|
||||||
|
|
||||||
b.Property<DateTime>("LastSeenAt")
|
b.Property<DateTime>("LastSeenAt")
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("last-seen-at");
|
.HasColumnName("last-seen-at");
|
||||||
@ -144,10 +148,18 @@ namespace FileShare_EFCore.Migrations.SQLite
|
|||||||
.HasColumnType("INTEGER")
|
.HasColumnType("INTEGER")
|
||||||
.HasColumnName("size-bytes");
|
.HasColumnName("size-bytes");
|
||||||
|
|
||||||
|
b.Property<int?>("ThumbnailId")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("thumbnail-id");
|
||||||
|
|
||||||
b.Property<DateTime>("UpdatedAt")
|
b.Property<DateTime>("UpdatedAt")
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("updated-at");
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.Property<double?>("VideoDuration")
|
||||||
|
.HasColumnType("REAL")
|
||||||
|
.HasColumnName("video-duration");
|
||||||
|
|
||||||
b.HasKey("Id")
|
b.HasKey("Id")
|
||||||
.HasName("pk-managed-file-record");
|
.HasName("pk-managed-file-record");
|
||||||
|
|
||||||
@ -155,9 +167,15 @@ namespace FileShare_EFCore.Migrations.SQLite
|
|||||||
.IsUnique()
|
.IsUnique()
|
||||||
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||||
|
|
||||||
|
b.HasIndex("LastPlayedAt")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||||
|
|
||||||
b.HasIndex("LibraryRootId")
|
b.HasIndex("LibraryRootId")
|
||||||
.HasDatabaseName("idx-managed-file-record-root-id");
|
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||||
|
|
||||||
|
b.HasIndex("ThumbnailId")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-thumbnail-id");
|
||||||
|
|
||||||
b.HasIndex("MediaType", "Exists")
|
b.HasIndex("MediaType", "Exists")
|
||||||
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||||
|
|
||||||
@ -234,6 +252,53 @@ namespace FileShare_EFCore.Migrations.SQLite
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("content-type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<int>("LibraryRootId")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("library-root-id");
|
||||||
|
|
||||||
|
b.Property<string>("RelativePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("relative-path");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-thumbnail-map");
|
||||||
|
|
||||||
|
b.HasIndex("LibraryRootId")
|
||||||
|
.HasDatabaseName("idx-managed-thumbnail-map-root-id");
|
||||||
|
|
||||||
|
b.HasIndex("RelativePath")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-thumbnail-map-relative-path");
|
||||||
|
|
||||||
|
b.ToTable("managed-thumbnail-map", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件缩略图映射记录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@ -336,10 +401,35 @@ namespace FileShare_EFCore.Migrations.SQLite
|
|||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedThumbnailMap", "Thumbnail")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("ThumbnailId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("LibraryRoot");
|
||||||
|
|
||||||
|
b.Navigation("Thumbnail");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||||
|
.WithMany("Thumbnails")
|
||||||
|
.HasForeignKey("LibraryRootId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
b.Navigation("LibraryRoot");
|
b.Navigation("LibraryRoot");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
|
||||||
|
b.Navigation("Thumbnails");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Files");
|
b.Navigation("Files");
|
||||||
});
|
});
|
||||||
|
|||||||
383
FileShare-EFCore/Migrations/SqlServer/20260522082829_AutoMigration_20260522162758.Designer.cs
generated
Normal file
383
FileShare-EFCore/Migrations/SqlServer/20260522082829_AutoMigration_20260522162758.Designer.cs
generated
Normal file
@ -0,0 +1,383 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using FileShare_EFCore.Database;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FileShare_EFCore.Migrations.SqlServer
|
||||||
|
{
|
||||||
|
[DbContext(typeof(SqlServerAppDataContext))]
|
||||||
|
[Migration("20260522082829_AutoMigration_20260522162758")]
|
||||||
|
partial class AutoMigration_20260522162758
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.7")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||||
|
|
||||||
|
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<string>("Device")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)")
|
||||||
|
.HasColumnName("device");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExpiresAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("expires-at");
|
||||||
|
|
||||||
|
b.Property<string>("IpAddress")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("nvarchar(64)")
|
||||||
|
.HasColumnName("ip-address");
|
||||||
|
|
||||||
|
b.Property<string>("ReplacedByTokenHash")
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("nvarchar(128)")
|
||||||
|
.HasColumnName("replaced-by-token-hash");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("RevokedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("revoked-at");
|
||||||
|
|
||||||
|
b.Property<string>("TokenHash")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("nvarchar(128)")
|
||||||
|
.HasColumnName("token-hash");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("user-id");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-api-refresh-token");
|
||||||
|
|
||||||
|
b.HasIndex("TokenHash")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-api-refresh-token-hash");
|
||||||
|
|
||||||
|
b.HasIndex("UserId")
|
||||||
|
.HasDatabaseName("idx-api-refresh-token-user-id");
|
||||||
|
|
||||||
|
b.ToTable("api-refresh-token", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("API refresh token");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("AbsolutePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("nvarchar(2048)")
|
||||||
|
.HasColumnName("absolute-path");
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)")
|
||||||
|
.HasColumnName("content-type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<bool>("Exists")
|
||||||
|
.HasColumnType("bit")
|
||||||
|
.HasColumnName("exists");
|
||||||
|
|
||||||
|
b.Property<string>("Extension")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("nvarchar(32)")
|
||||||
|
.HasColumnName("extension");
|
||||||
|
|
||||||
|
b.Property<string>("FileName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(260)
|
||||||
|
.HasColumnType("nvarchar(260)")
|
||||||
|
.HasColumnName("file-name");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastPlayedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("last-played-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastSeenAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("last-seen-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastWriteTimeUtc")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("last-write-time-utc");
|
||||||
|
|
||||||
|
b.Property<int>("LibraryRootId")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("library-root-id");
|
||||||
|
|
||||||
|
b.Property<string>("MediaType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)")
|
||||||
|
.HasColumnName("media-type");
|
||||||
|
|
||||||
|
b.Property<string>("RelativePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("nvarchar(1024)")
|
||||||
|
.HasColumnName("relative-path");
|
||||||
|
|
||||||
|
b.Property<long>("SizeBytes")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("size-bytes");
|
||||||
|
|
||||||
|
b.Property<string>("ThumbnailPath")
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("nvarchar(512)")
|
||||||
|
.HasColumnName("thumbnail-path");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.Property<double?>("VideoDuration")
|
||||||
|
.HasColumnType("float")
|
||||||
|
.HasColumnName("video-duration");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-file-record");
|
||||||
|
|
||||||
|
b.HasIndex("AbsolutePath")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||||
|
|
||||||
|
b.HasIndex("LastPlayedAt")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||||
|
|
||||||
|
b.HasIndex("LibraryRootId")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||||
|
|
||||||
|
b.HasIndex("MediaType", "Exists")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||||
|
|
||||||
|
b.ToTable("managed-file-record", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库文件记录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)")
|
||||||
|
.HasColumnName("display-name");
|
||||||
|
|
||||||
|
b.Property<bool>("IsAvailable")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bit")
|
||||||
|
.HasDefaultValue(true)
|
||||||
|
.HasColumnName("is-available");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEnabled")
|
||||||
|
.HasColumnType("bit")
|
||||||
|
.HasColumnName("is-enabled");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanCompletedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("last-scan-completed-at");
|
||||||
|
|
||||||
|
b.Property<string>("LastScanError")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("nvarchar(2000)")
|
||||||
|
.HasColumnName("last-scan-error");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanStartedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("last-scan-started-at");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("nvarchar(1024)")
|
||||||
|
.HasColumnName("path");
|
||||||
|
|
||||||
|
b.Property<int>("ScanIntervalMinutes")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("scan-interval-minutes");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-library-root");
|
||||||
|
|
||||||
|
b.HasIndex("Path")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-library-root-path");
|
||||||
|
|
||||||
|
b.ToTable("managed-library-root", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库根目录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id")
|
||||||
|
.HasComment("用户主键");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("created-at")
|
||||||
|
.HasComment("创建时间");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)")
|
||||||
|
.HasColumnName("email")
|
||||||
|
.HasComment("用户邮箱");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)")
|
||||||
|
.HasColumnName("name")
|
||||||
|
.HasComment("用户名称");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)")
|
||||||
|
.HasColumnName("password-hash")
|
||||||
|
.HasComment("密码哈希值");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)")
|
||||||
|
.HasColumnName("phone-number")
|
||||||
|
.HasComment("电话号码");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("updated-at")
|
||||||
|
.HasComment("更新时间");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-user");
|
||||||
|
|
||||||
|
b.ToTable("user", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("用户实体,演示数据库 CRUD 操作");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id")
|
||||||
|
.HasComment("天气预报主键");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("created-at")
|
||||||
|
.HasComment("创建时间");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("Date")
|
||||||
|
.HasColumnType("date")
|
||||||
|
.HasColumnName("date")
|
||||||
|
.HasComment("预报日期");
|
||||||
|
|
||||||
|
b.Property<string>("Summary")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)")
|
||||||
|
.HasColumnName("summary")
|
||||||
|
.HasComment("天气摘要");
|
||||||
|
|
||||||
|
b.Property<int>("TemperatureC")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("temperature-c")
|
||||||
|
.HasComment("摄氏温度");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("updated-at")
|
||||||
|
.HasComment("更新时间");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-weather-forecast");
|
||||||
|
|
||||||
|
b.ToTable("weather-forecast", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("天气预报数据实体");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("LibraryRootId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("LibraryRoot");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,110 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FileShare_EFCore.Migrations.SqlServer
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AutoMigration_20260522162758 : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "managed-library-root",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
|
path = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: false),
|
||||||
|
displayname = table.Column<string>(name: "display-name", type: "nvarchar(200)", maxLength: 200, nullable: false),
|
||||||
|
isenabled = table.Column<bool>(name: "is-enabled", type: "bit", nullable: false),
|
||||||
|
isavailable = table.Column<bool>(name: "is-available", type: "bit", nullable: false, defaultValue: true),
|
||||||
|
scanintervalminutes = table.Column<int>(name: "scan-interval-minutes", type: "int", nullable: false),
|
||||||
|
lastscanstartedat = table.Column<DateTime>(name: "last-scan-started-at", type: "datetime2", nullable: true),
|
||||||
|
lastscancompletedat = table.Column<DateTime>(name: "last-scan-completed-at", type: "datetime2", nullable: true),
|
||||||
|
lastscanerror = table.Column<string>(name: "last-scan-error", type: "nvarchar(2000)", maxLength: 2000, nullable: true),
|
||||||
|
createdat = table.Column<DateTime>(name: "created-at", type: "datetime2", nullable: false),
|
||||||
|
updatedat = table.Column<DateTime>(name: "updated-at", type: "datetime2", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk-managed-library-root", x => x.id);
|
||||||
|
},
|
||||||
|
comment: "文件库根目录");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "managed-file-record",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
|
libraryrootid = table.Column<int>(name: "library-root-id", type: "int", nullable: false),
|
||||||
|
filename = table.Column<string>(name: "file-name", type: "nvarchar(260)", maxLength: 260, nullable: false),
|
||||||
|
relativepath = table.Column<string>(name: "relative-path", type: "nvarchar(1024)", maxLength: 1024, nullable: false),
|
||||||
|
absolutepath = table.Column<string>(name: "absolute-path", type: "nvarchar(2048)", maxLength: 2048, nullable: false),
|
||||||
|
extension = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
|
||||||
|
sizebytes = table.Column<long>(name: "size-bytes", type: "bigint", nullable: false),
|
||||||
|
lastwritetimeutc = table.Column<DateTime>(name: "last-write-time-utc", type: "datetime2", nullable: false),
|
||||||
|
mediatype = table.Column<string>(name: "media-type", type: "nvarchar(20)", maxLength: 20, nullable: false),
|
||||||
|
contenttype = table.Column<string>(name: "content-type", type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||||
|
exists = table.Column<bool>(type: "bit", nullable: false),
|
||||||
|
lastseenat = table.Column<DateTime>(name: "last-seen-at", type: "datetime2", nullable: false),
|
||||||
|
thumbnailpath = table.Column<string>(name: "thumbnail-path", type: "nvarchar(512)", maxLength: 512, nullable: true),
|
||||||
|
videoduration = table.Column<double>(name: "video-duration", type: "float", nullable: true),
|
||||||
|
lastplayedat = table.Column<DateTime>(name: "last-played-at", type: "datetime2", nullable: true),
|
||||||
|
createdat = table.Column<DateTime>(name: "created-at", type: "datetime2", nullable: false),
|
||||||
|
updatedat = table.Column<DateTime>(name: "updated-at", type: "datetime2", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk-managed-file-record", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_managed-file-record_managed-library-root_library-root-id",
|
||||||
|
column: x => x.libraryrootid,
|
||||||
|
principalTable: "managed-library-root",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
},
|
||||||
|
comment: "文件库文件记录");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-file-record-absolute-path",
|
||||||
|
table: "managed-file-record",
|
||||||
|
column: "absolute-path",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-file-record-last-played-at",
|
||||||
|
table: "managed-file-record",
|
||||||
|
column: "last-played-at");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-file-record-media-type-exists",
|
||||||
|
table: "managed-file-record",
|
||||||
|
columns: new[] { "media-type", "exists" });
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-file-record-root-id",
|
||||||
|
table: "managed-file-record",
|
||||||
|
column: "library-root-id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-library-root-path",
|
||||||
|
table: "managed-library-root",
|
||||||
|
column: "path",
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "managed-library-root");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
459
FileShare-EFCore/Migrations/SqlServer/20260522084325_AddThumbnailMap.Designer.cs
generated
Normal file
459
FileShare-EFCore/Migrations/SqlServer/20260522084325_AddThumbnailMap.Designer.cs
generated
Normal file
@ -0,0 +1,459 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using FileShare_EFCore.Database;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FileShare_EFCore.Migrations.SqlServer
|
||||||
|
{
|
||||||
|
[DbContext(typeof(SqlServerAppDataContext))]
|
||||||
|
[Migration("20260522084325_AddThumbnailMap")]
|
||||||
|
partial class AddThumbnailMap
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.7")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||||
|
|
||||||
|
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<string>("Device")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)")
|
||||||
|
.HasColumnName("device");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExpiresAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("expires-at");
|
||||||
|
|
||||||
|
b.Property<string>("IpAddress")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("nvarchar(64)")
|
||||||
|
.HasColumnName("ip-address");
|
||||||
|
|
||||||
|
b.Property<string>("ReplacedByTokenHash")
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("nvarchar(128)")
|
||||||
|
.HasColumnName("replaced-by-token-hash");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("RevokedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("revoked-at");
|
||||||
|
|
||||||
|
b.Property<string>("TokenHash")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("nvarchar(128)")
|
||||||
|
.HasColumnName("token-hash");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("user-id");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-api-refresh-token");
|
||||||
|
|
||||||
|
b.HasIndex("TokenHash")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-api-refresh-token-hash");
|
||||||
|
|
||||||
|
b.HasIndex("UserId")
|
||||||
|
.HasDatabaseName("idx-api-refresh-token-user-id");
|
||||||
|
|
||||||
|
b.ToTable("api-refresh-token", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("API refresh token");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("AbsolutePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("nvarchar(2048)")
|
||||||
|
.HasColumnName("absolute-path");
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)")
|
||||||
|
.HasColumnName("content-type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<bool>("Exists")
|
||||||
|
.HasColumnType("bit")
|
||||||
|
.HasColumnName("exists");
|
||||||
|
|
||||||
|
b.Property<string>("Extension")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("nvarchar(32)")
|
||||||
|
.HasColumnName("extension");
|
||||||
|
|
||||||
|
b.Property<string>("FileName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(260)
|
||||||
|
.HasColumnType("nvarchar(260)")
|
||||||
|
.HasColumnName("file-name");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastPlayedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("last-played-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastSeenAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("last-seen-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastWriteTimeUtc")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("last-write-time-utc");
|
||||||
|
|
||||||
|
b.Property<int>("LibraryRootId")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("library-root-id");
|
||||||
|
|
||||||
|
b.Property<string>("MediaType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)")
|
||||||
|
.HasColumnName("media-type");
|
||||||
|
|
||||||
|
b.Property<string>("RelativePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("nvarchar(1024)")
|
||||||
|
.HasColumnName("relative-path");
|
||||||
|
|
||||||
|
b.Property<long>("SizeBytes")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("size-bytes");
|
||||||
|
|
||||||
|
b.Property<int?>("ThumbnailId")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("thumbnail-id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.Property<double?>("VideoDuration")
|
||||||
|
.HasColumnType("float")
|
||||||
|
.HasColumnName("video-duration");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-file-record");
|
||||||
|
|
||||||
|
b.HasIndex("AbsolutePath")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||||
|
|
||||||
|
b.HasIndex("LastPlayedAt")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||||
|
|
||||||
|
b.HasIndex("LibraryRootId")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||||
|
|
||||||
|
b.HasIndex("ThumbnailId")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-thumbnail-id");
|
||||||
|
|
||||||
|
b.HasIndex("MediaType", "Exists")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||||
|
|
||||||
|
b.ToTable("managed-file-record", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库文件记录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)")
|
||||||
|
.HasColumnName("display-name");
|
||||||
|
|
||||||
|
b.Property<bool>("IsAvailable")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bit")
|
||||||
|
.HasDefaultValue(true)
|
||||||
|
.HasColumnName("is-available");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEnabled")
|
||||||
|
.HasColumnType("bit")
|
||||||
|
.HasColumnName("is-enabled");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanCompletedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("last-scan-completed-at");
|
||||||
|
|
||||||
|
b.Property<string>("LastScanError")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("nvarchar(2000)")
|
||||||
|
.HasColumnName("last-scan-error");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanStartedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("last-scan-started-at");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("nvarchar(1024)")
|
||||||
|
.HasColumnName("path");
|
||||||
|
|
||||||
|
b.Property<int>("ScanIntervalMinutes")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("scan-interval-minutes");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-library-root");
|
||||||
|
|
||||||
|
b.HasIndex("Path")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-library-root-path");
|
||||||
|
|
||||||
|
b.ToTable("managed-library-root", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库根目录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)")
|
||||||
|
.HasColumnName("content-type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<int>("LibraryRootId")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("library-root-id");
|
||||||
|
|
||||||
|
b.Property<string>("RelativePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("nvarchar(1024)")
|
||||||
|
.HasColumnName("relative-path");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-thumbnail-map");
|
||||||
|
|
||||||
|
b.HasIndex("LibraryRootId")
|
||||||
|
.HasDatabaseName("idx-managed-thumbnail-map-root-id");
|
||||||
|
|
||||||
|
b.HasIndex("RelativePath")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-thumbnail-map-relative-path");
|
||||||
|
|
||||||
|
b.ToTable("managed-thumbnail-map", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件缩略图映射记录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id")
|
||||||
|
.HasComment("用户主键");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("created-at")
|
||||||
|
.HasComment("创建时间");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)")
|
||||||
|
.HasColumnName("email")
|
||||||
|
.HasComment("用户邮箱");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)")
|
||||||
|
.HasColumnName("name")
|
||||||
|
.HasComment("用户名称");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)")
|
||||||
|
.HasColumnName("password-hash")
|
||||||
|
.HasComment("密码哈希值");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)")
|
||||||
|
.HasColumnName("phone-number")
|
||||||
|
.HasComment("电话号码");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("updated-at")
|
||||||
|
.HasComment("更新时间");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-user");
|
||||||
|
|
||||||
|
b.ToTable("user", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("用户实体,演示数据库 CRUD 操作");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id")
|
||||||
|
.HasComment("天气预报主键");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("created-at")
|
||||||
|
.HasComment("创建时间");
|
||||||
|
|
||||||
|
b.Property<DateOnly>("Date")
|
||||||
|
.HasColumnType("date")
|
||||||
|
.HasColumnName("date")
|
||||||
|
.HasComment("预报日期");
|
||||||
|
|
||||||
|
b.Property<string>("Summary")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)")
|
||||||
|
.HasColumnName("summary")
|
||||||
|
.HasComment("天气摘要");
|
||||||
|
|
||||||
|
b.Property<int>("TemperatureC")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("temperature-c")
|
||||||
|
.HasComment("摄氏温度");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("updated-at")
|
||||||
|
.HasComment("更新时间");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-weather-forecast");
|
||||||
|
|
||||||
|
b.ToTable("weather-forecast", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("天气预报数据实体");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("LibraryRootId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedThumbnailMap", "Thumbnail")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("ThumbnailId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("LibraryRoot");
|
||||||
|
|
||||||
|
b.Navigation("Thumbnail");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||||
|
.WithMany("Thumbnails")
|
||||||
|
.HasForeignKey("LibraryRootId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("LibraryRoot");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
|
||||||
|
b.Navigation("Thumbnails");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,99 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FileShare_EFCore.Migrations.SqlServer
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddThumbnailMap : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "thumbnail-path",
|
||||||
|
table: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "thumbnail-id",
|
||||||
|
table: "managed-file-record",
|
||||||
|
type: "int",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "managed-thumbnail-map",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
|
libraryrootid = table.Column<int>(name: "library-root-id", type: "int", nullable: false),
|
||||||
|
relativepath = table.Column<string>(name: "relative-path", type: "nvarchar(1024)", maxLength: 1024, nullable: false),
|
||||||
|
contenttype = table.Column<string>(name: "content-type", type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||||
|
createdat = table.Column<DateTime>(name: "created-at", type: "datetime2", nullable: false),
|
||||||
|
updatedat = table.Column<DateTime>(name: "updated-at", type: "datetime2", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk-managed-thumbnail-map", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_managed-thumbnail-map_managed-library-root_library-root-id",
|
||||||
|
column: x => x.libraryrootid,
|
||||||
|
principalTable: "managed-library-root",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
},
|
||||||
|
comment: "文件缩略图映射记录");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-file-record-thumbnail-id",
|
||||||
|
table: "managed-file-record",
|
||||||
|
column: "thumbnail-id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-thumbnail-map-relative-path",
|
||||||
|
table: "managed-thumbnail-map",
|
||||||
|
column: "relative-path",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "idx-managed-thumbnail-map-root-id",
|
||||||
|
table: "managed-thumbnail-map",
|
||||||
|
column: "library-root-id");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_managed-file-record_managed-thumbnail-map_thumbnail-id",
|
||||||
|
table: "managed-file-record",
|
||||||
|
column: "thumbnail-id",
|
||||||
|
principalTable: "managed-thumbnail-map",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.SetNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_managed-file-record_managed-thumbnail-map_thumbnail-id",
|
||||||
|
table: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "managed-thumbnail-map");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "idx-managed-file-record-thumbnail-id",
|
||||||
|
table: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "thumbnail-id",
|
||||||
|
table: "managed-file-record");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "thumbnail-path",
|
||||||
|
table: "managed-file-record",
|
||||||
|
type: "nvarchar(512)",
|
||||||
|
maxLength: 512,
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
using System;
|
using System;
|
||||||
using FileShare_EFCore.Database;
|
using FileShare_EFCore.Database;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -84,6 +84,234 @@ namespace FileShare_EFCore.Migrations.SqlServer
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("AbsolutePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("nvarchar(2048)")
|
||||||
|
.HasColumnName("absolute-path");
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)")
|
||||||
|
.HasColumnName("content-type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<bool>("Exists")
|
||||||
|
.HasColumnType("bit")
|
||||||
|
.HasColumnName("exists");
|
||||||
|
|
||||||
|
b.Property<string>("Extension")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("nvarchar(32)")
|
||||||
|
.HasColumnName("extension");
|
||||||
|
|
||||||
|
b.Property<string>("FileName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(260)
|
||||||
|
.HasColumnType("nvarchar(260)")
|
||||||
|
.HasColumnName("file-name");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastPlayedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("last-played-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastSeenAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("last-seen-at");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastWriteTimeUtc")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("last-write-time-utc");
|
||||||
|
|
||||||
|
b.Property<int>("LibraryRootId")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("library-root-id");
|
||||||
|
|
||||||
|
b.Property<string>("MediaType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)")
|
||||||
|
.HasColumnName("media-type");
|
||||||
|
|
||||||
|
b.Property<string>("RelativePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("nvarchar(1024)")
|
||||||
|
.HasColumnName("relative-path");
|
||||||
|
|
||||||
|
b.Property<long>("SizeBytes")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("size-bytes");
|
||||||
|
|
||||||
|
b.Property<int?>("ThumbnailId")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("thumbnail-id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.Property<double?>("VideoDuration")
|
||||||
|
.HasColumnType("float")
|
||||||
|
.HasColumnName("video-duration");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-file-record");
|
||||||
|
|
||||||
|
b.HasIndex("AbsolutePath")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||||
|
|
||||||
|
b.HasIndex("LastPlayedAt")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||||
|
|
||||||
|
b.HasIndex("LibraryRootId")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||||
|
|
||||||
|
b.HasIndex("ThumbnailId")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-thumbnail-id");
|
||||||
|
|
||||||
|
b.HasIndex("MediaType", "Exists")
|
||||||
|
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||||
|
|
||||||
|
b.ToTable("managed-file-record", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库文件记录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)")
|
||||||
|
.HasColumnName("display-name");
|
||||||
|
|
||||||
|
b.Property<bool>("IsAvailable")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bit")
|
||||||
|
.HasDefaultValue(true)
|
||||||
|
.HasColumnName("is-available");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEnabled")
|
||||||
|
.HasColumnType("bit")
|
||||||
|
.HasColumnName("is-enabled");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanCompletedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("last-scan-completed-at");
|
||||||
|
|
||||||
|
b.Property<string>("LastScanError")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("nvarchar(2000)")
|
||||||
|
.HasColumnName("last-scan-error");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastScanStartedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("last-scan-started-at");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("nvarchar(1024)")
|
||||||
|
.HasColumnName("path");
|
||||||
|
|
||||||
|
b.Property<int>("ScanIntervalMinutes")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("scan-interval-minutes");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-library-root");
|
||||||
|
|
||||||
|
b.HasIndex("Path")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-library-root-path");
|
||||||
|
|
||||||
|
b.ToTable("managed-library-root", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件库根目录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)")
|
||||||
|
.HasColumnName("content-type");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("created-at");
|
||||||
|
|
||||||
|
b.Property<int>("LibraryRootId")
|
||||||
|
.HasColumnType("int")
|
||||||
|
.HasColumnName("library-root-id");
|
||||||
|
|
||||||
|
b.Property<string>("RelativePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("nvarchar(1024)")
|
||||||
|
.HasColumnName("relative-path");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2")
|
||||||
|
.HasColumnName("updated-at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk-managed-thumbnail-map");
|
||||||
|
|
||||||
|
b.HasIndex("LibraryRootId")
|
||||||
|
.HasDatabaseName("idx-managed-thumbnail-map-root-id");
|
||||||
|
|
||||||
|
b.HasIndex("RelativePath")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("idx-managed-thumbnail-map-relative-path");
|
||||||
|
|
||||||
|
b.ToTable("managed-thumbnail-map", t =>
|
||||||
|
{
|
||||||
|
t.HasComment("文件缩略图映射记录");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@ -181,6 +409,47 @@ namespace FileShare_EFCore.Migrations.SqlServer
|
|||||||
t.HasComment("天气预报数据实体");
|
t.HasComment("天气预报数据实体");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("LibraryRootId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedThumbnailMap", "Thumbnail")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("ThumbnailId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("LibraryRoot");
|
||||||
|
|
||||||
|
b.Navigation("Thumbnail");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||||
|
.WithMany("Thumbnails")
|
||||||
|
.HasForeignKey("LibraryRootId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("LibraryRoot");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
|
||||||
|
b.Navigation("Thumbnails");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,6 +67,18 @@ namespace FileShare_EFCore.Models
|
|||||||
[Column("last-seen-at")]
|
[Column("last-seen-at")]
|
||||||
public DateTime LastSeenAt { get; set; } = DateTime.UtcNow;
|
public DateTime LastSeenAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
/// <summary>视频缩略图路径(相对于 wwwroot)。</summary>
|
||||||
|
[Column("thumbnail-id")]
|
||||||
|
public int? ThumbnailId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>视频时长(秒)。</summary>
|
||||||
|
[Column("video-duration")]
|
||||||
|
public double? VideoDuration { get; set; }
|
||||||
|
|
||||||
|
/// <summary>最近一次播放时间 UTC。</summary>
|
||||||
|
[Column("last-played-at")]
|
||||||
|
public DateTime? LastPlayedAt { get; set; }
|
||||||
|
|
||||||
/// <summary>创建时间。</summary>
|
/// <summary>创建时间。</summary>
|
||||||
[Column("created-at")]
|
[Column("created-at")]
|
||||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
@ -77,5 +89,7 @@ namespace FileShare_EFCore.Models
|
|||||||
|
|
||||||
/// <summary>所属根目录。</summary>
|
/// <summary>所属根目录。</summary>
|
||||||
public ManagedLibraryRoot? LibraryRoot { get; set; }
|
public ManagedLibraryRoot? LibraryRoot { get; set; }
|
||||||
|
|
||||||
|
public ManagedThumbnailMap? Thumbnail { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,5 +62,7 @@ namespace FileShare_EFCore.Models
|
|||||||
|
|
||||||
/// <summary>文件记录。</summary>
|
/// <summary>文件记录。</summary>
|
||||||
public List<ManagedFileRecord> Files { get; set; } = new();
|
public List<ManagedFileRecord> Files { get; set; } = new();
|
||||||
|
|
||||||
|
public List<ManagedThumbnailMap> Thumbnails { get; set; } = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
37
FileShare-EFCore/Models/ManagedThumbnailMap.cs
Normal file
37
FileShare-EFCore/Models/ManagedThumbnailMap.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace FileShare_EFCore.Models
|
||||||
|
{
|
||||||
|
[Comment("文件缩略图映射记录")]
|
||||||
|
[Table("managed-thumbnail-map")]
|
||||||
|
public class ManagedThumbnailMap
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[Column("id")]
|
||||||
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[Column("library-root-id")]
|
||||||
|
public int LibraryRootId { get; set; }
|
||||||
|
|
||||||
|
[Column("relative-path")]
|
||||||
|
[MaxLength(1024)]
|
||||||
|
public string RelativePath { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Column("content-type")]
|
||||||
|
[MaxLength(100)]
|
||||||
|
public string ContentType { get; set; } = "image/jpeg";
|
||||||
|
|
||||||
|
[Column("created-at")]
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
[Column("updated-at")]
|
||||||
|
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
public ManagedLibraryRoot? LibraryRoot { get; set; }
|
||||||
|
|
||||||
|
public List<ManagedFileRecord> Files { get; set; } = new();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,6 +13,8 @@
|
|||||||
<Content Include="www\**\*">
|
<Content Include="www\**\*">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Include="..\tools\ffmpeg\bin\ffmpeg.exe" Link="tools\ffmpeg\bin\ffmpeg.exe" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
|
||||||
|
<Content Include="..\tools\ffmpeg\bin\ffprobe.exe" Link="tools\ffmpeg\bin\ffprobe.exe" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -12,6 +12,7 @@ using FileShare_Services.Services.FileLibrary;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace FileShare_PC
|
namespace FileShare_PC
|
||||||
{
|
{
|
||||||
@ -71,9 +72,13 @@ namespace FileShare_PC
|
|||||||
services.AddSingleton<PcGlobalTokenService>();
|
services.AddSingleton<PcGlobalTokenService>();
|
||||||
services.AddSingleton<IAuthService, PcAuthService>();
|
services.AddSingleton<IAuthService, PcAuthService>();
|
||||||
services.AddSingleton<IPcAuthEndpointService, PcAuthEndpointService>();
|
services.AddSingleton<IPcAuthEndpointService, PcAuthEndpointService>();
|
||||||
|
services.AddSingleton(new ThumbnailStorageOptions());
|
||||||
|
services.AddSingleton<IVideoThumbnailService>(sp =>
|
||||||
|
new VideoThumbnailService(sp.GetRequiredService<ThumbnailStorageOptions>()));
|
||||||
services.AddScoped<IFileLibraryService, FileLibraryService>();
|
services.AddScoped<IFileLibraryService, FileLibraryService>();
|
||||||
services.AddScoped<IFileLibraryEndpointService, FileLibraryEndpointService>();
|
services.AddScoped<IFileLibraryEndpointService, FileLibraryEndpointService>();
|
||||||
services.AddScoped<IFileStreamService, FileStreamService>();
|
services.AddScoped<IFileStreamService, FileStreamService>();
|
||||||
|
services.AddScoped<IThumbnailStreamService, ThumbnailStreamService>();
|
||||||
|
|
||||||
// ---- 端点注册 ----
|
// ---- 端点注册 ----
|
||||||
var endpointBuilder = new ServiceEndpointBuilder();
|
var endpointBuilder = new ServiceEndpointBuilder();
|
||||||
|
|||||||
@ -554,7 +554,8 @@ namespace FileShare_PC.Views
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (await TryHandleLocalMediaStreamAsync(context))
|
if (await TryHandleLocalMediaStreamAsync(context)
|
||||||
|
|| await TryHandleLocalThumbnailAsync(context))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -691,6 +692,69 @@ namespace FileShare_PC.Views
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<bool> TryHandleLocalThumbnailAsync(HttpListenerContext context)
|
||||||
|
{
|
||||||
|
var request = context.Request;
|
||||||
|
var response = context.Response;
|
||||||
|
var segments = (request.Url?.AbsolutePath ?? string.Empty)
|
||||||
|
.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
|
|
||||||
|
if (segments.Length != 3
|
||||||
|
|| !string.Equals(segments[0], "api", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| !string.Equals(segments[1], "thumbnails", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddLocalMediaHeaders(response);
|
||||||
|
|
||||||
|
if (!string.Equals(request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& !string.Equals(request.HttpMethod, "HEAD", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
response.StatusCode = (int)HttpStatusCode.MethodNotAllowed;
|
||||||
|
response.Close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!int.TryParse(segments[2], out var id) || id <= 0)
|
||||||
|
{
|
||||||
|
response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||||
|
response.Close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var scope = _services.CreateScope();
|
||||||
|
var thumbnailService = scope.ServiceProvider.GetService<IThumbnailStreamService>();
|
||||||
|
var thumbnail = thumbnailService is null ? null : await thumbnailService.GetThumbnailAsync(id);
|
||||||
|
if (thumbnail is null || !File.Exists(thumbnail.FilePath))
|
||||||
|
{
|
||||||
|
response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||||
|
response.Close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileInfo = new FileInfo(thumbnail.FilePath);
|
||||||
|
response.StatusCode = (int)HttpStatusCode.OK;
|
||||||
|
response.ContentType = thumbnail.ContentType;
|
||||||
|
response.ContentLength64 = fileInfo.Length;
|
||||||
|
response.Headers["Cache-Control"] = "public, max-age=3600";
|
||||||
|
response.Headers["Content-Disposition"] =
|
||||||
|
$"inline; filename=\"{Uri.EscapeDataString(thumbnail.FileName)}\"";
|
||||||
|
response.Headers["Last-Modified"] = thumbnail.LastModified.ToUniversalTime().ToString("R");
|
||||||
|
|
||||||
|
if (string.Equals(request.HttpMethod, "HEAD", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| fileInfo.Length == 0)
|
||||||
|
{
|
||||||
|
response.Close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await using var input = File.Open(thumbnail.FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||||
|
await input.CopyToAsync(response.OutputStream);
|
||||||
|
response.OutputStream.Close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 为媒体流响应添加 CORS 和 Range 请求头,允许浏览器跨域访问和分段请求。
|
/// 为媒体流响应添加 CORS 和 Range 请求头,允许浏览器跨域访问和分段请求。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -69,6 +69,14 @@ namespace FileShare_Services.Endpoints
|
|||||||
.WithOpenApi("FileLibrary", "浏览文件库目录结构。")
|
.WithOpenApi("FileLibrary", "浏览文件库目录结构。")
|
||||||
.WithName("BrowseDirectory");
|
.WithName("BrowseDirectory");
|
||||||
|
|
||||||
|
endpoints.MapGet<IFileLibraryEndpointService, RecentFilesRequest>("api/files/recent", (service, request, _) => service.GetRecentFilesAsync(request))
|
||||||
|
.WithOpenApi("FileLibrary", "获取最近添加或最近播放的文件。")
|
||||||
|
.WithName("GetRecentFiles");
|
||||||
|
|
||||||
|
endpoints.MapPost<IFileLibraryEndpointService, MarkFilePlayedRequest>("api/files/played", (service, request, _) => service.MarkFilePlayedAsync(request))
|
||||||
|
.WithOpenApi("FileLibrary", "标记文件已播放。")
|
||||||
|
.WithName("MarkFilePlayed");
|
||||||
|
|
||||||
endpoints.MapGet<IFileLibraryEndpointService, FileQueryRequest>("api/files/detail", (service, request, _) => service.GetFileAsync(request))
|
endpoints.MapGet<IFileLibraryEndpointService, FileQueryRequest>("api/files/detail", (service, request, _) => service.GetFileAsync(request))
|
||||||
.WithOpenApi("FileLibrary", "查询文件详情。")
|
.WithOpenApi("FileLibrary", "查询文件详情。")
|
||||||
.WithName("GetFileDetail");
|
.WithName("GetFileDetail");
|
||||||
|
|||||||
@ -46,6 +46,19 @@ namespace FileShare_Services.Services.FileLibrary
|
|||||||
public sealed record FileQueryRequest(
|
public sealed record FileQueryRequest(
|
||||||
[property: JsonPropertyName("id")] int Id);
|
[property: JsonPropertyName("id")] int Id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取最近文件的请求。
|
||||||
|
/// </summary>
|
||||||
|
public sealed record RecentFilesRequest(
|
||||||
|
[property: JsonPropertyName("type")] string Type = "added",
|
||||||
|
[property: JsonPropertyName("count")] int Count = 12);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 标记文件已播放的请求。
|
||||||
|
/// </summary>
|
||||||
|
public sealed record MarkFilePlayedRequest(
|
||||||
|
[property: JsonPropertyName("id")] int Id);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 分页搜索已扫描文件的请求。
|
/// 分页搜索已扫描文件的请求。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -105,7 +118,10 @@ namespace FileShare_Services.Services.FileLibrary
|
|||||||
string ContentType,
|
string ContentType,
|
||||||
string StreamUrl,
|
string StreamUrl,
|
||||||
string? TextUrl,
|
string? TextUrl,
|
||||||
bool BrowserPlayable);
|
bool BrowserPlayable,
|
||||||
|
string? ThumbnailUrl = null,
|
||||||
|
double? VideoDuration = null,
|
||||||
|
DateTime? LastPlayedAt = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 浏览文件库目录结构的请求。
|
/// 浏览文件库目录结构的请求。
|
||||||
|
|||||||
@ -29,7 +29,7 @@ namespace FileShare_Services.Services.FileLibrary
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IApiResponse> AddRootAsync(AddLibraryRootRequest request)
|
public async Task<IApiResponse> AddRootAsync(AddLibraryRootRequest request)
|
||||||
{
|
{
|
||||||
return ResponseHelper.Ok(await fileLibrary.AddRootAsync(request), "文件库目录已添加并完成扫描。");
|
return ResponseHelper.Ok(await fileLibrary.AddRootAsync(request), "文件库目录已添加,后续扫描将自动入库。");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -87,6 +87,20 @@ namespace FileShare_Services.Services.FileLibrary
|
|||||||
return ResponseHelper.Ok(result);
|
return ResponseHelper.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<IApiResponse> GetRecentFilesAsync(RecentFilesRequest request)
|
||||||
|
{
|
||||||
|
var items = await fileLibrary.GetRecentFilesAsync(request.Type, request.Count);
|
||||||
|
return ResponseHelper.Ok(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<IApiResponse> MarkFilePlayedAsync(MarkFilePlayedRequest request)
|
||||||
|
{
|
||||||
|
await fileLibrary.MarkFilePlayedAsync(request.Id);
|
||||||
|
return ResponseHelper.Succeed();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 验证文件 ID 是否有效,无效时抛出 <see cref="ArgumentException"/>。
|
/// 验证文件 ID 是否有效,无效时抛出 <see cref="ArgumentException"/>。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -10,7 +10,7 @@ namespace FileShare_Services.Services.FileLibrary
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 文件库核心业务服务,实现磁盘枚举、目录管理、文件扫描与检索。
|
/// 文件库核心业务服务,实现磁盘枚举、目录管理、文件扫描与检索。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class FileLibraryService(AppDataContext db) : IFileLibraryService
|
public sealed class FileLibraryService(AppDataContext db, IVideoThumbnailService thumbnailService) : IFileLibraryService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 默认扫描间隔(分钟),当请求未指定间隔时使用。
|
/// 默认扫描间隔(分钟),当请求未指定间隔时使用。
|
||||||
@ -80,7 +80,9 @@ namespace FileShare_Services.Services.FileLibrary
|
|||||||
existing.DisplayName = ResolveDisplayName(normalized, request.DisplayName);
|
existing.DisplayName = ResolveDisplayName(normalized, request.DisplayName);
|
||||||
existing.ScanIntervalMinutes = NormalizeInterval(request.ScanIntervalMinutes);
|
existing.ScanIntervalMinutes = NormalizeInterval(request.ScanIntervalMinutes);
|
||||||
await db.SaveChangesAsync(cancellationToken);
|
await db.SaveChangesAsync(cancellationToken);
|
||||||
return await ScanRootAsync(existing.Id, cancellationToken);
|
var existingCount = await db.ManagedFileRecords
|
||||||
|
.CountAsync(file => file.LibraryRootId == existing.Id && file.Exists, cancellationToken);
|
||||||
|
return ToRootDto(existing, existingCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
var root = new ManagedLibraryRoot
|
var root = new ManagedLibraryRoot
|
||||||
@ -95,7 +97,7 @@ namespace FileShare_Services.Services.FileLibrary
|
|||||||
db.ManagedLibraryRoots.Add(root);
|
db.ManagedLibraryRoots.Add(root);
|
||||||
await db.SaveChangesAsync(cancellationToken);
|
await db.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
return await ScanRootAsync(root.Id, cancellationToken);
|
return ToRootDto(root, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -178,6 +180,24 @@ namespace FileShare_Services.Services.FileLibrary
|
|||||||
record.ContentType = contentType;
|
record.ContentType = contentType;
|
||||||
record.Exists = true;
|
record.Exists = true;
|
||||||
record.LastSeenAt = DateTime.UtcNow;
|
record.LastSeenAt = DateTime.UtcNow;
|
||||||
|
|
||||||
|
if (mediaType == "video" && record.ThumbnailId is null)
|
||||||
|
{
|
||||||
|
var thumbnail = await thumbnailService.GenerateThumbnailAsync(root.Id, absolutePath, cancellationToken);
|
||||||
|
if (thumbnail is not null)
|
||||||
|
{
|
||||||
|
var map = new ManagedThumbnailMap
|
||||||
|
{
|
||||||
|
LibraryRootId = root.Id,
|
||||||
|
RelativePath = thumbnail.RelativePath,
|
||||||
|
ContentType = thumbnail.ContentType,
|
||||||
|
};
|
||||||
|
db.ManagedThumbnailMaps.Add(map);
|
||||||
|
record.Thumbnail = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
record.VideoDuration ??= thumbnailService.GetVideoDuration(absolutePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var stale in existing.Values.Where(file => !seen.Contains(file.AbsolutePath)))
|
foreach (var stale in existing.Values.Where(file => !seen.Contains(file.AbsolutePath)))
|
||||||
@ -356,6 +376,34 @@ namespace FileShare_Services.Services.FileLibrary
|
|||||||
return new TextPreviewDto(file.Id, file.FileName, content, stream.Length > MaxTextPreviewBytes);
|
return new TextPreviewDto(file.Id, file.FileName, content, stream.Length > MaxTextPreviewBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<List<FileRecordDto>> GetRecentFilesAsync(string type, int count = 12, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var query = db.ManagedFileRecords
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(file => file.Exists && file.LibraryRoot != null && file.LibraryRoot.IsAvailable);
|
||||||
|
|
||||||
|
query = type == "played"
|
||||||
|
? query.Where(file => file.LastPlayedAt != null).OrderByDescending(file => file.LastPlayedAt)
|
||||||
|
: query.OrderByDescending(file => file.CreatedAt);
|
||||||
|
|
||||||
|
return await query
|
||||||
|
.Take(Math.Clamp(count, 1, 48))
|
||||||
|
.Select(file => ToFileDto(file))
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task MarkFilePlayedAsync(int id, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var record = await db.ManagedFileRecords.FindAsync([id], cancellationToken);
|
||||||
|
if (record is not null)
|
||||||
|
{
|
||||||
|
record.LastPlayedAt = DateTime.UtcNow;
|
||||||
|
await db.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 深度优先遍历目录树,枚举所有被 <see cref="MediaFileTypes"/> 支持的媒体文件路径。
|
/// 深度优先遍历目录树,枚举所有被 <see cref="MediaFileTypes"/> 支持的媒体文件路径。
|
||||||
/// 遇到无权限的目录时跳过该分支继续遍历。
|
/// 遇到无权限的目录时跳过该分支继续遍历。
|
||||||
@ -508,7 +556,10 @@ namespace FileShare_Services.Services.FileLibrary
|
|||||||
file.ContentType,
|
file.ContentType,
|
||||||
$"/api/files/{file.Id}/stream",
|
$"/api/files/{file.Id}/stream",
|
||||||
file.MediaType == "text" ? $"/api/files/text?id={file.Id}" : null,
|
file.MediaType == "text" ? $"/api/files/text?id={file.Id}" : null,
|
||||||
MediaFileTypes.IsBrowserPlayable(file.Extension));
|
MediaFileTypes.IsBrowserPlayable(file.Extension),
|
||||||
|
file.ThumbnailId is null ? null : $"/api/thumbnails/{file.ThumbnailId}",
|
||||||
|
file.VideoDuration,
|
||||||
|
file.LastPlayedAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,5 +84,9 @@ namespace FileShare_Services.Services.FileLibrary
|
|||||||
/// <param name="request">包含根目录 ID 和路径的请求。</param>
|
/// <param name="request">包含根目录 ID 和路径的请求。</param>
|
||||||
/// <returns>API 响应。</returns>
|
/// <returns>API 响应。</returns>
|
||||||
Task<IApiResponse> BrowseDirectoryAsync(BrowseDirectoryRequest request);
|
Task<IApiResponse> BrowseDirectoryAsync(BrowseDirectoryRequest request);
|
||||||
|
|
||||||
|
Task<IApiResponse> GetRecentFilesAsync(RecentFilesRequest request);
|
||||||
|
|
||||||
|
Task<IApiResponse> MarkFilePlayedAsync(MarkFilePlayedRequest request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,5 +97,9 @@ namespace FileShare_Services.Services.FileLibrary
|
|||||||
/// <param name="cancellationToken">取消令牌。</param>
|
/// <param name="cancellationToken">取消令牌。</param>
|
||||||
/// <returns>目录浏览响应,包含子目录和文件列表。</returns>
|
/// <returns>目录浏览响应,包含子目录和文件列表。</returns>
|
||||||
Task<BrowseDirectoryResponse> BrowseDirectoryAsync(BrowseDirectoryRequest request, CancellationToken cancellationToken = default);
|
Task<BrowseDirectoryResponse> BrowseDirectoryAsync(BrowseDirectoryRequest request, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
Task<List<FileRecordDto>> GetRecentFilesAsync(string type, int count = 12, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
Task MarkFilePlayedAsync(int id, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
namespace FileShare_Services.Services.FileLibrary
|
||||||
|
{
|
||||||
|
public interface IVideoThumbnailService
|
||||||
|
{
|
||||||
|
Task<GeneratedThumbnail?> GenerateThumbnailAsync(int libraryRootId, string videoPath, CancellationToken ct = default);
|
||||||
|
string GetAbsolutePath(string relativePath);
|
||||||
|
double? GetVideoDuration(string videoPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record GeneratedThumbnail(string RelativePath, string ContentType);
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
namespace FileShare_Services.Services.FileLibrary
|
||||||
|
{
|
||||||
|
public sealed class ThumbnailStorageOptions
|
||||||
|
{
|
||||||
|
public string RootPath { get; set; } = Path.Combine(AppContext.BaseDirectory, "thumbnails");
|
||||||
|
public string FfmpegPath { get; set; } = Path.Combine("tools", "ffmpeg", "bin", "ffmpeg.exe");
|
||||||
|
public string FfprobePath { get; set; } = Path.Combine("tools", "ffmpeg", "bin", "ffprobe.exe");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
using FileShare_EFCore.Database;
|
||||||
|
using FileShare_Services.Core;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace FileShare_Services.Services.FileLibrary
|
||||||
|
{
|
||||||
|
public interface IThumbnailStreamService
|
||||||
|
{
|
||||||
|
Task<FileStreamResponse?> GetThumbnailAsync(int id, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ThumbnailStreamService(AppDataContext db, IVideoThumbnailService thumbnails) : IThumbnailStreamService
|
||||||
|
{
|
||||||
|
public async Task<FileStreamResponse?> GetThumbnailAsync(int id, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var thumbnail = await db.ManagedThumbnailMaps
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(item => item.Id == id, cancellationToken);
|
||||||
|
|
||||||
|
if (thumbnail is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var absolutePath = thumbnails.GetAbsolutePath(thumbnail.RelativePath);
|
||||||
|
if (!File.Exists(absolutePath))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FileStreamResponse(
|
||||||
|
absolutePath,
|
||||||
|
Path.GetFileName(absolutePath),
|
||||||
|
thumbnail.ContentType,
|
||||||
|
File.GetLastWriteTimeUtc(absolutePath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
136
FileShare-Services/Services/FileLibrary/VideoThumbnailService.cs
Normal file
136
FileShare-Services/Services/FileLibrary/VideoThumbnailService.cs
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace FileShare_Services.Services.FileLibrary
|
||||||
|
{
|
||||||
|
public sealed class VideoThumbnailService : IVideoThumbnailService
|
||||||
|
{
|
||||||
|
private readonly string _thumbnailDir;
|
||||||
|
private readonly string _ffmpegPath;
|
||||||
|
private readonly string _ffprobePath;
|
||||||
|
|
||||||
|
public VideoThumbnailService(ThumbnailStorageOptions options)
|
||||||
|
{
|
||||||
|
_thumbnailDir = Path.IsPathRooted(options.RootPath)
|
||||||
|
? options.RootPath
|
||||||
|
: Path.Combine(AppContext.BaseDirectory, options.RootPath);
|
||||||
|
_ffmpegPath = ResolveExecutablePath(options.FfmpegPath);
|
||||||
|
_ffprobePath = ResolveExecutablePath(options.FfprobePath);
|
||||||
|
Directory.CreateDirectory(_thumbnailDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<GeneratedThumbnail?> GenerateThumbnailAsync(int libraryRootId, string videoPath, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var hash = ComputeHash(videoPath);
|
||||||
|
var relativePath = Path.Combine($"root-{libraryRootId}", $"{hash}.jpg");
|
||||||
|
var outputPath = GetAbsolutePath(relativePath);
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(outputPath)!);
|
||||||
|
if (File.Exists(outputPath))
|
||||||
|
return Task.FromResult<GeneratedThumbnail?>(new GeneratedThumbnail(relativePath, "image/jpeg"));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var args = $"-ss 00:00:01 -i \"{videoPath}\" -vframes 1 -q:v 5 -vf \"scale=320:-2\" \"{outputPath}\" -y";
|
||||||
|
|
||||||
|
using var process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = _ffmpegPath,
|
||||||
|
Arguments = args,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
},
|
||||||
|
EnableRaisingEvents = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
process.Start();
|
||||||
|
|
||||||
|
using (ct.Register(() =>
|
||||||
|
{
|
||||||
|
try { process.Kill(); } catch { /* 进程可能已退出 */ }
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
process.WaitForExit(10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(outputPath) && new FileInfo(outputPath).Length > 0)
|
||||||
|
return Task.FromResult<GeneratedThumbnail?>(new GeneratedThumbnail(relativePath, "image/jpeg"));
|
||||||
|
|
||||||
|
return Task.FromResult<GeneratedThumbnail?>(null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Serilog.Log.Warning(ex, "生成视频缩略图失败 {VideoPath}", videoPath);
|
||||||
|
return Task.FromResult<GeneratedThumbnail?>(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetAbsolutePath(string relativePath)
|
||||||
|
{
|
||||||
|
var root = Path.GetFullPath(_thumbnailDir);
|
||||||
|
var fullPath = Path.GetFullPath(Path.Combine(root, relativePath));
|
||||||
|
if (!fullPath.StartsWith(root + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& !string.Equals(fullPath, root, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Thumbnail path is outside of the configured storage root.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ResolveExecutablePath(string executablePath)
|
||||||
|
{
|
||||||
|
if (Path.IsPathRooted(executablePath))
|
||||||
|
{
|
||||||
|
return executablePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return executablePath.Contains(Path.DirectorySeparatorChar)
|
||||||
|
|| executablePath.Contains(Path.AltDirectorySeparatorChar)
|
||||||
|
? Path.Combine(AppContext.BaseDirectory, executablePath)
|
||||||
|
: executablePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double? GetVideoDuration(string videoPath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var args = $"-v error -show_entries format=duration -of csv=p=0 \"{videoPath}\"";
|
||||||
|
|
||||||
|
using var process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = _ffprobePath,
|
||||||
|
Arguments = args,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
process.Start();
|
||||||
|
var output = process.StandardOutput.ReadToEnd().Trim();
|
||||||
|
process.WaitForExit(5000);
|
||||||
|
|
||||||
|
if (double.TryParse(output, out var duration) && duration > 0)
|
||||||
|
return Math.Round(duration, 1);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Serilog.Log.Warning(ex, "获取视频时长失败 {VideoPath}", videoPath);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ComputeHash(string videoPath) =>
|
||||||
|
Convert.ToHexString(MD5.HashData(Encoding.UTF8.GetBytes(videoPath))).ToLowerInvariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -43,6 +43,9 @@ export interface FileRecordDto {
|
|||||||
streamUrl: string
|
streamUrl: string
|
||||||
textUrl: string | null
|
textUrl: string | null
|
||||||
browserPlayable: boolean
|
browserPlayable: boolean
|
||||||
|
thumbnailUrl: string | null
|
||||||
|
videoDuration: number | null
|
||||||
|
lastPlayedAt: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BrowseDirectoryResponse {
|
export interface BrowseDirectoryResponse {
|
||||||
@ -98,5 +101,10 @@ export const api = {
|
|||||||
getTextPreview: (id: number) =>
|
getTextPreview: (id: number) =>
|
||||||
request<TextPreviewDto>(`files/text${qs({ id })}`),
|
request<TextPreviewDto>(`files/text${qs({ id })}`),
|
||||||
mediaUrl: (path: string) => apiUrl(path),
|
mediaUrl: (path: string) => apiUrl(path),
|
||||||
|
thumbnailUrl: (path: string) => apiUrl(path),
|
||||||
|
getRecentFiles: (type: string, count = 12) =>
|
||||||
|
request<FileRecordDto[]>(`files/recent${qs({ type, count })}`),
|
||||||
|
markFilePlayed: (id: number) =>
|
||||||
|
request('files/played', { method: 'POST', body: { id } }),
|
||||||
qrCode: () => request<{ url: string; qrCodeBase64: string }>('qrcode'),
|
qrCode: () => request<{ url: string; qrCodeBase64: string }>('qrcode'),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -498,19 +498,6 @@ a {
|
|||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player-panel video {
|
|
||||||
width: 100%;
|
|
||||||
max-height: 54vh;
|
|
||||||
margin-top: 12px;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: #111827;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player-panel audio {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.open-media-link {
|
.open-media-link {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -524,19 +511,6 @@ a {
|
|||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player-panel pre {
|
|
||||||
overflow: auto;
|
|
||||||
max-height: 54vh;
|
|
||||||
margin: 12px 0 0;
|
|
||||||
border: 1px solid var(--line);
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 12px;
|
|
||||||
background: #f8fafc;
|
|
||||||
color: #111827;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-list {
|
.mobile-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 9px;
|
gap: 9px;
|
||||||
@ -724,6 +698,257 @@ a {
|
|||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Root tabs */
|
||||||
|
.root-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 4px;
|
||||||
|
background: var(--panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
.root-tabs button {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 36px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--muted);
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root-tabs button.active {
|
||||||
|
color: #fff;
|
||||||
|
background: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* View toggle */
|
||||||
|
.view-toggle-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-toggle {
|
||||||
|
display: flex;
|
||||||
|
gap: 0;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-toggle button {
|
||||||
|
min-height: 30px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 4px 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--muted);
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-toggle button.active {
|
||||||
|
color: #fff;
|
||||||
|
background: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-toggle button + button {
|
||||||
|
border-left: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section header with view toggle inline */
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* File grid / card view */
|
||||||
|
.file-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 10px;
|
||||||
|
background: var(--panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-card {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: left;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-card.active {
|
||||||
|
border-color: var(--accent);
|
||||||
|
box-shadow: 0 0 0 2px var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-card:hover:not(:disabled) {
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-thumb {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 16 / 10;
|
||||||
|
object-fit: contain;
|
||||||
|
background: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-thumb-placeholder {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
aspect-ratio: 16 / 10;
|
||||||
|
border-radius: 999px;
|
||||||
|
margin: 12px auto 0;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
color: #fff;
|
||||||
|
background: var(--accent-strong);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
display: grid;
|
||||||
|
align-content: start;
|
||||||
|
gap: 3px;
|
||||||
|
padding: 10px 10px 12px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body strong {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body small {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* File thumbnail in list view */
|
||||||
|
.file-thumb {
|
||||||
|
flex: 0 0 72px;
|
||||||
|
width: 72px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 6px;
|
||||||
|
object-fit: contain;
|
||||||
|
background: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list .mobile-file {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 60px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list .mobile-file > span:last-child {
|
||||||
|
display: grid;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Video info bar */
|
||||||
|
.video-info-bar {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 8px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-info-bar span {
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 3px 9px;
|
||||||
|
background: #f2f4f7;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Player media wrapper */
|
||||||
|
.player-media-wrapper {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-media-wrapper video {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 54vh;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-media-wrapper audio {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-media-wrapper pre {
|
||||||
|
overflow: auto;
|
||||||
|
max-height: 54vh;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #111827;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unsupported-player {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unsupported-thumb {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 360px;
|
||||||
|
margin: 0 auto 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
object-fit: contain;
|
||||||
|
background: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Recent files section */
|
||||||
|
.recent-files {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 1100px) {
|
@media (max-width: 1100px) {
|
||||||
.admin-layout,
|
.admin-layout,
|
||||||
.admin-browser {
|
.admin-browser {
|
||||||
|
|||||||
@ -15,6 +15,12 @@ const rootId = ref<number | undefined>()
|
|||||||
const browsePath = ref<string[]>([])
|
const browsePath = ref<string[]>([])
|
||||||
const isBrowsingRoots = ref(true)
|
const isBrowsingRoots = ref(true)
|
||||||
|
|
||||||
|
// Recent files & tabs
|
||||||
|
const activeTab = ref<'recent-added' | 'recent-played' | 'libraries'>('libraries')
|
||||||
|
const recentFiles = ref<FileRecordDto[]>([])
|
||||||
|
const recentLoading = ref(false)
|
||||||
|
const viewMode = ref<'list' | 'grid'>('list')
|
||||||
|
|
||||||
const qrModal = ref<InstanceType<typeof QrCodeModal> | null>(null)
|
const qrModal = ref<InstanceType<typeof QrCodeModal> | null>(null)
|
||||||
|
|
||||||
const activeRoots = computed(() => roots.value.filter((root) => root.isEnabled && root.isAvailable))
|
const activeRoots = computed(() => roots.value.filter((root) => root.isEnabled && root.isAvailable))
|
||||||
@ -34,8 +40,13 @@ const breadcrumbs = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const selectedMediaUrl = computed(() => selectedFile.value ? api.mediaUrl(selectedFile.value.streamUrl) : '')
|
const selectedMediaUrl = computed(() => selectedFile.value ? api.mediaUrl(selectedFile.value.streamUrl) : '')
|
||||||
|
const selectedThumbnailUrl = computed(() =>
|
||||||
|
selectedFile.value?.thumbnailUrl ? api.thumbnailUrl(selectedFile.value.thumbnailUrl) : ''
|
||||||
|
)
|
||||||
|
|
||||||
const clientTitle = computed(() => {
|
const clientTitle = computed(() => {
|
||||||
|
if (activeTab.value === 'recent-added') return '最近添加'
|
||||||
|
if (activeTab.value === 'recent-played') return '最近播放'
|
||||||
if (isBrowsingRoots.value) return '文件库'
|
if (isBrowsingRoots.value) return '文件库'
|
||||||
const root = roots.value.find((r) => r.id === rootId.value)
|
const root = roots.value.find((r) => r.id === rootId.value)
|
||||||
const dir = browsePath.value.length > 0 ? browsePath.value[browsePath.value.length - 1] : ''
|
const dir = browsePath.value.length > 0 ? browsePath.value[browsePath.value.length - 1] : ''
|
||||||
@ -54,6 +65,13 @@ function formatSize(bytes: number) {
|
|||||||
return `${value.toFixed(value >= 10 ? 1 : 2)} ${units[index]}`
|
return `${value.toFixed(value >= 10 ? 1 : 2)} ${units[index]}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatDuration(seconds: number | null) {
|
||||||
|
if (!seconds) return ''
|
||||||
|
const m = Math.floor(seconds / 60)
|
||||||
|
const s = Math.floor(seconds % 60)
|
||||||
|
return `${m}:${s.toString().padStart(2, '0')}`
|
||||||
|
}
|
||||||
|
|
||||||
function formatDate(value: string | null) {
|
function formatDate(value: string | null) {
|
||||||
if (!value) return ''
|
if (!value) return ''
|
||||||
return new Date(value).toLocaleString()
|
return new Date(value).toLocaleString()
|
||||||
@ -80,12 +98,35 @@ async function browseDirectory() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadRecentFiles(type: string) {
|
||||||
|
try {
|
||||||
|
errorMessage.value = ''
|
||||||
|
recentLoading.value = true
|
||||||
|
recentFiles.value = await api.getRecentFiles(type, 24)
|
||||||
|
} catch (error) {
|
||||||
|
setError(error)
|
||||||
|
} finally {
|
||||||
|
recentLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchTab(tab: 'recent-added' | 'recent-played' | 'libraries') {
|
||||||
|
activeTab.value = tab
|
||||||
|
isBrowsingRoots.value = true
|
||||||
|
browseData.value = null
|
||||||
|
selectedFile.value = null
|
||||||
|
textPreview.value = null
|
||||||
|
if (tab === 'recent-added') loadRecentFiles('added')
|
||||||
|
else if (tab === 'recent-played') loadRecentFiles('played')
|
||||||
|
}
|
||||||
|
|
||||||
async function enterRoot(id: number) {
|
async function enterRoot(id: number) {
|
||||||
rootId.value = id
|
rootId.value = id
|
||||||
isBrowsingRoots.value = false
|
isBrowsingRoots.value = false
|
||||||
browsePath.value = []
|
browsePath.value = []
|
||||||
selectedFile.value = null
|
selectedFile.value = null
|
||||||
textPreview.value = null
|
textPreview.value = null
|
||||||
|
activeTab.value = 'libraries'
|
||||||
await browseDirectory()
|
await browseDirectory()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +137,7 @@ function backToRoots() {
|
|||||||
browsePath.value = []
|
browsePath.value = []
|
||||||
selectedFile.value = null
|
selectedFile.value = null
|
||||||
textPreview.value = null
|
textPreview.value = null
|
||||||
|
activeTab.value = 'libraries'
|
||||||
}
|
}
|
||||||
|
|
||||||
async function navigateTo(path: string) {
|
async function navigateTo(path: string) {
|
||||||
@ -119,6 +161,11 @@ async function enterSubdirectory(name: string) {
|
|||||||
async function selectFile(file: FileRecordDto) {
|
async function selectFile(file: FileRecordDto) {
|
||||||
selectedFile.value = file
|
selectedFile.value = file
|
||||||
textPreview.value = null
|
textPreview.value = null
|
||||||
|
|
||||||
|
if (file.mediaType === 'video' || file.mediaType === 'audio') {
|
||||||
|
api.markFilePlayed(file.id).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
if (file.mediaType !== 'text') return
|
if (file.mediaType !== 'text') return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -146,7 +193,9 @@ onMounted(async () => {
|
|||||||
<div>
|
<div>
|
||||||
<h1>{{ clientTitle }}</h1>
|
<h1>{{ clientTitle }}</h1>
|
||||||
<p>
|
<p>
|
||||||
<template v-if="isBrowsingRoots">{{ activeRoots.length }} 个目录</template>
|
<template v-if="activeTab === 'recent-added'">{{ recentFiles.length }} 个文件</template>
|
||||||
|
<template v-else-if="activeTab === 'recent-played'">{{ recentFiles.length }} 个文件</template>
|
||||||
|
<template v-else-if="isBrowsingRoots">{{ activeRoots.length }} 个目录</template>
|
||||||
<template v-else-if="browseData">
|
<template v-else-if="browseData">
|
||||||
{{ browseData.subdirectories.length }} 个文件夹 · {{ browseData.files.length }} 个文件
|
{{ browseData.subdirectories.length }} 个文件夹 · {{ browseData.files.length }} 个文件
|
||||||
</template>
|
</template>
|
||||||
@ -161,8 +210,99 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<p v-if="errorMessage" class="error-banner">{{ errorMessage }}</p>
|
<p v-if="errorMessage" class="error-banner">{{ errorMessage }}</p>
|
||||||
|
|
||||||
|
<!-- Root tabs -->
|
||||||
|
<nav v-if="isBrowsingRoots" class="root-tabs">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:class="{ active: activeTab === 'recent-added' }"
|
||||||
|
@click="switchTab('recent-added')"
|
||||||
|
>最近添加</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:class="{ active: activeTab === 'recent-played' }"
|
||||||
|
@click="switchTab('recent-played')"
|
||||||
|
>最近播放</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:class="{ active: activeTab === 'libraries' }"
|
||||||
|
@click="switchTab('libraries')"
|
||||||
|
>文件库</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Recently added / played files -->
|
||||||
|
<section v-if="isBrowsingRoots && activeTab !== 'libraries'" class="recent-files">
|
||||||
|
<div class="view-toggle-bar">
|
||||||
|
<div></div>
|
||||||
|
<div class="view-toggle">
|
||||||
|
<button type="button" :class="{ active: viewMode === 'list' }" @click="viewMode = 'list'">列表</button>
|
||||||
|
<button type="button" :class="{ active: viewMode === 'grid' }" @click="viewMode = 'grid'">网格</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p v-if="recentLoading" class="empty-state">加载中...</p>
|
||||||
|
<p v-else-if="recentFiles.length === 0" class="empty-state">
|
||||||
|
{{ activeTab === 'recent-added' ? '暂无最近添加的文件' : '暂无播放记录' }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Grid view -->
|
||||||
|
<div v-else-if="viewMode === 'grid'" class="file-grid">
|
||||||
|
<button
|
||||||
|
v-for="file in recentFiles"
|
||||||
|
:key="file.id"
|
||||||
|
class="file-card"
|
||||||
|
type="button"
|
||||||
|
@click="selectFile(file)"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="file.thumbnailUrl"
|
||||||
|
:src="api.thumbnailUrl(file.thumbnailUrl)"
|
||||||
|
class="card-thumb"
|
||||||
|
alt=""
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
<div v-else class="card-thumb-placeholder">{{ file.mediaType }}</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<strong>{{ file.fileName }}</strong>
|
||||||
|
<small>
|
||||||
|
{{ formatSize(file.sizeBytes) }}
|
||||||
|
<template v-if="file.videoDuration"> · {{ formatDuration(file.videoDuration) }}</template>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- List view -->
|
||||||
|
<div v-else class="file-list">
|
||||||
|
<button
|
||||||
|
v-for="file in recentFiles"
|
||||||
|
:key="file.id"
|
||||||
|
class="mobile-file"
|
||||||
|
@click="selectFile(file)"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="file.thumbnailUrl"
|
||||||
|
:src="api.thumbnailUrl(file.thumbnailUrl)"
|
||||||
|
class="file-thumb"
|
||||||
|
alt=""
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
<span v-else class="type-badge">{{ file.mediaType }}</span>
|
||||||
|
<span>
|
||||||
|
<strong>{{ file.fileName }}</strong>
|
||||||
|
<small>
|
||||||
|
{{ formatSize(file.sizeBytes) }}
|
||||||
|
<template v-if="file.videoDuration"> · {{ formatDuration(file.videoDuration) }}</template>
|
||||||
|
<template v-if="file.lastPlayedAt && activeTab === 'recent-played'">
|
||||||
|
· {{ formatDate(file.lastPlayedAt) }}
|
||||||
|
</template>
|
||||||
|
</small>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- Library root tiles -->
|
<!-- Library root tiles -->
|
||||||
<section v-if="isBrowsingRoots" class="root-picker">
|
<section v-if="isBrowsingRoots && activeTab === 'libraries'" class="root-picker">
|
||||||
<button
|
<button
|
||||||
v-for="root in activeRoots"
|
v-for="root in activeRoots"
|
||||||
:key="root.id"
|
:key="root.id"
|
||||||
@ -178,7 +318,7 @@ onMounted(async () => {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Directory browser -->
|
<!-- Directory browser -->
|
||||||
<template v-else>
|
<template v-else-if="!isBrowsingRoots">
|
||||||
<!-- Breadcrumb -->
|
<!-- Breadcrumb -->
|
||||||
<nav class="breadcrumb-nav">
|
<nav class="breadcrumb-nav">
|
||||||
<template v-for="(crumb, index) in breadcrumbs" :key="crumb.path">
|
<template v-for="(crumb, index) in breadcrumbs" :key="crumb.path">
|
||||||
@ -222,26 +362,46 @@ onMounted(async () => {
|
|||||||
<span>{{ selectedFile.extension }}</span>
|
<span>{{ selectedFile.extension }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<video
|
<div v-if="selectedFile.mediaType === 'video'" class="video-info-bar">
|
||||||
v-if="selectedFile.mediaType === 'video' && selectedFile.browserPlayable"
|
<span v-if="selectedFile.videoDuration">时长 {{ formatDuration(selectedFile.videoDuration) }}</span>
|
||||||
:key="selectedFile.id"
|
<span>{{ formatSize(selectedFile.sizeBytes) }}</span>
|
||||||
controls
|
<span>{{ selectedFile.contentType }}</span>
|
||||||
playsinline
|
</div>
|
||||||
webkit-playsinline
|
|
||||||
preload="metadata"
|
<div class="player-media-wrapper">
|
||||||
>
|
<video
|
||||||
<source :src="selectedMediaUrl" :type="selectedFile.contentType" />
|
v-if="selectedFile.mediaType === 'video' && selectedFile.browserPlayable"
|
||||||
</video>
|
:key="selectedFile.id"
|
||||||
<audio
|
:poster="selectedThumbnailUrl || undefined"
|
||||||
v-else-if="selectedFile.mediaType === 'audio' && selectedFile.browserPlayable"
|
controls
|
||||||
:key="selectedFile.id"
|
playsinline
|
||||||
controls
|
webkit-playsinline
|
||||||
preload="metadata"
|
preload="metadata"
|
||||||
>
|
>
|
||||||
<source :src="selectedMediaUrl" :type="selectedFile.contentType" />
|
<source :src="selectedMediaUrl" :type="selectedFile.contentType" />
|
||||||
</audio>
|
</video>
|
||||||
<pre v-else-if="selectedFile.mediaType === 'text'">{{ textPreview?.content ?? '加载中...' }}</pre>
|
<div
|
||||||
<p v-else class="unsupported">浏览器不支持在线播放此格式。</p>
|
v-else-if="selectedFile.mediaType === 'video' && !selectedFile.browserPlayable"
|
||||||
|
class="unsupported-player"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="selectedThumbnailUrl"
|
||||||
|
:src="selectedThumbnailUrl"
|
||||||
|
class="unsupported-thumb"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
<p class="unsupported">浏览器不支持在线播放此格式。</p>
|
||||||
|
</div>
|
||||||
|
<audio
|
||||||
|
v-else-if="selectedFile.mediaType === 'audio' && selectedFile.browserPlayable"
|
||||||
|
:key="selectedFile.id"
|
||||||
|
controls
|
||||||
|
preload="metadata"
|
||||||
|
>
|
||||||
|
<source :src="selectedMediaUrl" :type="selectedFile.contentType" />
|
||||||
|
</audio>
|
||||||
|
<pre v-else-if="selectedFile.mediaType === 'text'">{{ textPreview?.content ?? '加载中...' }}</pre>
|
||||||
|
</div>
|
||||||
<a
|
<a
|
||||||
v-if="selectedFile.mediaType !== 'text'"
|
v-if="selectedFile.mediaType !== 'text'"
|
||||||
class="open-media-link"
|
class="open-media-link"
|
||||||
@ -256,8 +416,44 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<!-- Files -->
|
<!-- Files -->
|
||||||
<section v-if="browseData.files.length > 0" class="browse-section">
|
<section v-if="browseData.files.length > 0" class="browse-section">
|
||||||
<h3>文件</h3>
|
<div class="section-header">
|
||||||
<div class="file-list">
|
<h3>文件</h3>
|
||||||
|
<div class="view-toggle">
|
||||||
|
<button type="button" :class="{ active: viewMode === 'list' }" @click="viewMode = 'list'">列表</button>
|
||||||
|
<button type="button" :class="{ active: viewMode === 'grid' }" @click="viewMode = 'grid'">网格</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Grid view -->
|
||||||
|
<div v-if="viewMode === 'grid'" class="file-grid">
|
||||||
|
<button
|
||||||
|
v-for="file in browseData.files"
|
||||||
|
:key="file.id"
|
||||||
|
class="file-card"
|
||||||
|
:class="{ active: selectedFile?.id === file.id }"
|
||||||
|
type="button"
|
||||||
|
@click="selectFile(file)"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="file.thumbnailUrl"
|
||||||
|
:src="api.thumbnailUrl(file.thumbnailUrl)"
|
||||||
|
class="card-thumb"
|
||||||
|
alt=""
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
<div v-else class="card-thumb-placeholder">{{ file.mediaType }}</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<strong>{{ file.fileName }}</strong>
|
||||||
|
<small>
|
||||||
|
{{ formatSize(file.sizeBytes) }}
|
||||||
|
<template v-if="file.videoDuration"> · {{ formatDuration(file.videoDuration) }}</template>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- List view -->
|
||||||
|
<div v-else class="file-list">
|
||||||
<button
|
<button
|
||||||
v-for="file in browseData.files"
|
v-for="file in browseData.files"
|
||||||
:key="file.id"
|
:key="file.id"
|
||||||
@ -266,10 +462,20 @@ onMounted(async () => {
|
|||||||
type="button"
|
type="button"
|
||||||
@click="selectFile(file)"
|
@click="selectFile(file)"
|
||||||
>
|
>
|
||||||
<span class="type-badge">{{ file.mediaType }}</span>
|
<img
|
||||||
|
v-if="file.thumbnailUrl"
|
||||||
|
:src="api.thumbnailUrl(file.thumbnailUrl)"
|
||||||
|
class="file-thumb"
|
||||||
|
alt=""
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
<span v-else class="type-badge">{{ file.mediaType }}</span>
|
||||||
<span>
|
<span>
|
||||||
<strong>{{ file.fileName }}</strong>
|
<strong>{{ file.fileName }}</strong>
|
||||||
<small>{{ formatSize(file.sizeBytes) }} · {{ formatDate(file.lastWriteTimeUtc) }}</small>
|
<small>
|
||||||
|
{{ formatSize(file.sizeBytes) }}
|
||||||
|
<template v-if="file.videoDuration"> · {{ formatDuration(file.videoDuration) }}</template>
|
||||||
|
</small>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
BIN
tools/ffmpeg/bin/ffmpeg.exe
Normal file
BIN
tools/ffmpeg/bin/ffmpeg.exe
Normal file
Binary file not shown.
BIN
tools/ffmpeg/bin/ffprobe.exe
Normal file
BIN
tools/ffmpeg/bin/ffprobe.exe
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user