Compare commits
16
Commits
a16c32b25e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
809ca92464 | ||
|
|
6bdcb48c97 | ||
|
|
87f5c1ffb8 | ||
|
|
60878f9b6e | ||
|
|
8c92d2fbac | ||
|
|
8d9c7f17ff | ||
|
|
c6b05c12e5 | ||
|
|
27e4029f4a | ||
|
|
6ef410fdfa | ||
|
|
ff19f4759f | ||
|
|
2c20f9bb54 | ||
|
|
6acc92ca27 | ||
|
|
d93098638d | ||
|
|
9f8da2c063 | ||
|
|
8270cf198b | ||
|
|
d84bbb3a18 |
+27
-28
@@ -2,36 +2,35 @@
|
||||
# 此 .gitignore 文件已由 Microsoft(R) Visual Studio 自动创建。
|
||||
################################################################################
|
||||
|
||||
/Avalonia-PC/bin
|
||||
/Avalonia-PC/.vs
|
||||
/Avalonia-PC/obj
|
||||
/Avalonia-API/bin
|
||||
/Avalonia-API/obj
|
||||
/Avalonia-Services/bin
|
||||
/Avalonia-Services/obj
|
||||
/Avalonia-Web-VUE/.vscode
|
||||
/Avalonia-Web-VUE/obj
|
||||
/Avalonia-Web-VUE/node_modules
|
||||
/Avalonia-Web-VUE/dist
|
||||
/Avalonia-Web-VUE/.vscode
|
||||
/avalonia-web-react/obj
|
||||
/avalonia-web-react/obj
|
||||
/avalonia-web-react/node_modules
|
||||
/avalonia-web-react/dist
|
||||
/Avalonia-EFCore/bin
|
||||
/Avalonia-EFCore/obj
|
||||
/Avalonia-Common/bin
|
||||
/Avalonia-Common/obj
|
||||
/Avalonia-API/logs
|
||||
/Avalonia-API/avalonia-api.db
|
||||
/Avalonia-API/avalonia-api.db-shm
|
||||
/Avalonia-API/avalonia-api.db-wal
|
||||
/Avalonia-API/app.db
|
||||
/Avalonia-API/app.db-shm
|
||||
/Avalonia-API/app.db-wal
|
||||
/Avalonia-API/wwwroot
|
||||
/FileShare-PC/bin
|
||||
/FileShare-PC/.vs
|
||||
/FileShare-PC/obj
|
||||
/FileShare-API/bin
|
||||
/FileShare-API/obj
|
||||
/FileShare-Services/bin
|
||||
/FileShare-Services/obj
|
||||
/FileShare-Web-VUE/.vscode
|
||||
/FileShare-Web-VUE/obj
|
||||
/FileShare-Web-VUE/node_modules
|
||||
/FileShare-Web-VUE/dist
|
||||
/FileShare-Web-VUE/.vscode
|
||||
/FileShare-EFCore/bin
|
||||
/FileShare-EFCore/obj
|
||||
/FileShare-Common/bin
|
||||
/FileShare-Common/obj
|
||||
/FileShare-API/logs
|
||||
/FileShare-API/fileshare-api.db
|
||||
/FileShare-API/fileshare-api.db-shm
|
||||
/FileShare-API/fileshare-api.db-wal
|
||||
/FileShare-API/app.db
|
||||
/FileShare-API/app.db-shm
|
||||
/FileShare-API/app.db-wal
|
||||
/FileShare-API/wwwroot
|
||||
/package-output
|
||||
/package-scripts/tools
|
||||
/.vs
|
||||
/bin
|
||||
/obj
|
||||
/.claude
|
||||
/.codex-build
|
||||
/FileShare-API/Properties
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# Codex Rules
|
||||
|
||||
- Do not manually edit files under `FileShare-EFCore/Migrations/**`. These files are generated by EF scripts.
|
||||
- When database schema changes are needed, update the EF model/configuration only and ask the user to generate migrations with the project scripts.
|
||||
@@ -1,50 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<RootNamespace>Avalonia_API</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="8.1.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Avalonia-Services\Avalonia-Services.csproj" />
|
||||
<ProjectReference Include="..\Avalonia-Common\Avalonia-Common.csproj" />
|
||||
<ProjectReference Include="..\Avalonia-EFCore\Avalonia-EFCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Controllers\" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="RestoreFrontendPackages" BeforeTargets="Build" Condition="'$(SkipFrontendBuild)' != 'true' And Exists('..\Avalonia-Web-VUE\package.json') And !Exists('..\Avalonia-Web-VUE\node_modules')">
|
||||
<Message Importance="high" Text="Restoring Avalonia-Web-VUE npm packages..." />
|
||||
<Exec WorkingDirectory="..\Avalonia-Web-VUE" Command="npm.cmd install" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildFrontend" BeforeTargets="Build" DependsOnTargets="RestoreFrontendPackages" Condition="'$(SkipFrontendBuild)' != 'true' And Exists('..\Avalonia-Web-VUE\package.json')">
|
||||
<Message Importance="high" Text="Building Avalonia-Web-VUE into Avalonia-API/wwwroot..." />
|
||||
<Exec WorkingDirectory="..\Avalonia-Web-VUE" Command="npm.cmd run build-only" />
|
||||
<RemoveDir Directories="wwwroot" />
|
||||
<MakeDir Directories="wwwroot" />
|
||||
<ItemGroup>
|
||||
<FrontendDist Include="..\Avalonia-Web-VUE\dist\**\*.*" />
|
||||
</ItemGroup>
|
||||
<Copy
|
||||
SourceFiles="@(FrontendDist)"
|
||||
DestinationFiles="@(FrontendDist->'wwwroot\%(RecursiveDir)%(Filename)%(Extension)')"
|
||||
SkipUnchangedFiles="false" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
@@ -1,6 +0,0 @@
|
||||
@Avalonia_API_HostAddress = http://localhost:5206
|
||||
|
||||
GET {{Avalonia_API_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
@@ -1,47 +0,0 @@
|
||||
using Avalonia_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Avalonia_API.Extensions
|
||||
{
|
||||
public static class FileStreamEndpointExtensions
|
||||
{
|
||||
public static IEndpointRouteBuilder MapFileStreamEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
app.MapMethods("/api/files/{id:int}/stream", ["GET", "HEAD"], async (int id, AppDataContext db, HttpContext httpContext) =>
|
||||
{
|
||||
// Browsers cancel in-flight range requests aggressively while seeking.
|
||||
// Keep this small metadata lookup independent from RequestAborted so
|
||||
// EF does not throw TaskCanceledException before the file is opened.
|
||||
var file = await db.ManagedFileRecords
|
||||
.AsNoTracking()
|
||||
.Include(item => item.LibraryRoot)
|
||||
.FirstOrDefaultAsync(item =>
|
||||
item.Id == id
|
||||
&& item.Exists
|
||||
&& item.LibraryRoot != null
|
||||
&& item.LibraryRoot.IsAvailable);
|
||||
|
||||
if (file is null || !System.IO.File.Exists(file.AbsolutePath))
|
||||
{
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
var stream = System.IO.File.Open(file.AbsolutePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
httpContext.Response.Headers.ContentDisposition = $"inline; filename=\"{Uri.EscapeDataString(file.FileName)}\"";
|
||||
httpContext.Response.Headers.AcceptRanges = "bytes";
|
||||
httpContext.Response.Headers.CacheControl = "public, max-age=3600";
|
||||
|
||||
return Results.File(
|
||||
stream,
|
||||
contentType: file.ContentType,
|
||||
fileDownloadName: null,
|
||||
lastModified: file.LastWriteTimeUtc,
|
||||
enableRangeProcessing: true);
|
||||
})
|
||||
.WithName("StreamManagedFile")
|
||||
.WithTags("FileLibrary");
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using Avalonia_Services.Services.FileLibrary;
|
||||
|
||||
namespace Avalonia_API.Services
|
||||
{
|
||||
public sealed class FileLibraryScanHostedService(IServiceScopeFactory scopeFactory, ILogger<FileLibraryScanHostedService> logger)
|
||||
: BackgroundService
|
||||
{
|
||||
private static readonly TimeSpan Interval = TimeSpan.FromMinutes(1);
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
await ScanAsync(stoppingToken);
|
||||
|
||||
using var timer = new PeriodicTimer(Interval);
|
||||
while (await timer.WaitForNextTickAsync(stoppingToken))
|
||||
{
|
||||
await ScanAsync(stoppingToken);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ScanAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var scope = scopeFactory.CreateAsyncScope();
|
||||
var scanner = scope.ServiceProvider.GetRequiredService<IFileLibraryService>();
|
||||
await scanner.ScanDueRootsAsync(cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(ex, "文件库定时扫描失败。");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Avalonia_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.MySQL
|
||||
{
|
||||
[DbContext(typeof(MySqlAppDataContext))]
|
||||
partial class MySqlAppDataContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
modelBuilder.Entity("Avalonia_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("Avalonia_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("Avalonia_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("天气预报数据实体");
|
||||
});
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Avalonia_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.PostgreSQL
|
||||
{
|
||||
[DbContext(typeof(PostgreSqlAppDataContext))]
|
||||
partial class PostgreSqlAppDataContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Avalonia_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("Avalonia_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("Avalonia_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("天气预报数据实体");
|
||||
});
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Avalonia_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.SqlServer
|
||||
{
|
||||
[DbContext(typeof(SqlServerAppDataContext))]
|
||||
partial class SqlServerAppDataContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Avalonia_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("Avalonia_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("Avalonia_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("天气预报数据实体");
|
||||
});
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<Solution>
|
||||
<Project Path="../Avalonia-API/Avalonia-API.csproj" Id="e33aba9a-a56b-4f6b-8eaa-3acbed65ebad" />
|
||||
<Project Path="../Avalonia-Common/Avalonia-Common.csproj" Id="caed4118-2161-4382-90b8-35fb4efe3b5f" />
|
||||
<Project Path="../Avalonia-EFCore/Avalonia-EFCore.csproj" Id="64557501-62a7-4863-b2bf-1570b8c6fecb" />
|
||||
<Project Path="../Avalonia-Services/Avalonia-Services.csproj" Id="b8757cf9-5422-4c67-acae-3c967c95f866" />
|
||||
<Project Path="../Avalonia-Web-VUE/avalonia-web-vue.esproj">
|
||||
<Build />
|
||||
<Deploy />
|
||||
</Project>
|
||||
<Project Path="Avalonia-PC.csproj" />
|
||||
</Solution>
|
||||
@@ -1,188 +0,0 @@
|
||||
using Avalonia_Common.Core;
|
||||
using Avalonia_EFCore.Database;
|
||||
using Avalonia_EFCore.Models;
|
||||
using Avalonia_Services.Core;
|
||||
using Avalonia_Services.Services;
|
||||
using Avalonia_Services.Services.FileLibrary;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Avalonia_Services.Endpoints
|
||||
{
|
||||
/// <summary>
|
||||
/// 统一端点配置 —— 所有业务端点在此定义一次。
|
||||
/// 这是 Avalonia-API 和 Avalonia-PC 的唯一入口。
|
||||
/// </summary>
|
||||
public static class AppEndpoints
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置所有业务端点。调用方传入 builder,按需叠加鉴权、过滤器等。
|
||||
/// </summary>
|
||||
/// <param name="builder">端点构建器</param>
|
||||
/// <param name="includeDetails">是否在错误响应中包含异常详情(开发环境 true)</param>
|
||||
public static ServiceEndpointBuilder Configure(ServiceEndpointBuilder builder, bool includeDetails = false)
|
||||
{
|
||||
// ---- 全局异常拦截(自动捕获所有端点中未处理的异常) ----
|
||||
builder.Endpoints.AddGlobalFilter(new GlobalExceptionFilter(includeDetails));
|
||||
|
||||
builder.ConfigureEndpoints(endpoints =>
|
||||
{
|
||||
// ---- 全局日志过滤器(记录每个请求) ----
|
||||
endpoints.AddGlobalFilter(async (ctx, next) =>
|
||||
{
|
||||
Serilog.Log.Debug("→ {Method} {Path}", ctx.Method, ctx.Path);
|
||||
await next(ctx);
|
||||
Serilog.Log.Debug("← {Method} {Path} | {StatusCode}", ctx.Method, ctx.Path, ctx.StatusCode);
|
||||
});
|
||||
|
||||
// ---- 业务端点注册 ----
|
||||
// 天气预报(从数据库读取)
|
||||
endpoints.MapGet("api/wData", GetWeatherForecastsAsync)
|
||||
.WithOpenApi("Weather", "获取天气预报信息。")
|
||||
.WithName("GetWeatherForecast");
|
||||
|
||||
// 获取用户(演示从数据库查询)
|
||||
endpoints.MapGet("api/getUser", GetUserFromDatabaseAsync)
|
||||
.WithName("GetUser");
|
||||
|
||||
// 处理数据(POST — 演示参数处理)
|
||||
endpoints.MapPost("api/processData", ProcessDataAsync)
|
||||
.WithName("ProcessData");
|
||||
|
||||
endpoints.MapGet<IFileLibraryEndpointService>("api/library/drives", (service, ctx) => service.GetDrivesAsync(ctx))
|
||||
.WithOpenApi("FileLibrary", "查询服务器磁盘。")
|
||||
.WithName("GetLibraryDrives");
|
||||
|
||||
endpoints.MapGet<IFileLibraryEndpointService>("api/library/directories", (service, ctx) => service.GetDirectoriesAsync(ctx))
|
||||
.WithOpenApi("FileLibrary", "查询服务器目录。")
|
||||
.WithName("GetLibraryDirectories");
|
||||
|
||||
endpoints.MapGet<IFileLibraryEndpointService>("api/library/roots", (service, ctx) => service.GetRootsAsync(ctx))
|
||||
.WithOpenApi("FileLibrary", "查询文件库目录。")
|
||||
.WithName("GetLibraryRoots");
|
||||
|
||||
endpoints.MapPost<IFileLibraryEndpointService>("api/library/roots", (service, ctx) => service.AddRootAsync(ctx))
|
||||
.WithOpenApi("FileLibrary", "添加文件库目录。")
|
||||
.WithName("AddLibraryRoot");
|
||||
|
||||
endpoints.MapPost<IFileLibraryEndpointService>("api/library/roots/enabled", (service, ctx) => service.SetRootEnabledAsync(ctx))
|
||||
.WithOpenApi("FileLibrary", "启用或禁用文件库目录。")
|
||||
.WithName("SetLibraryRootEnabled");
|
||||
|
||||
endpoints.MapPost<IFileLibraryEndpointService>("api/library/roots/delete", (service, ctx) => service.DeleteRootAsync(ctx))
|
||||
.WithOpenApi("FileLibrary", "删除文件库目录。")
|
||||
.WithName("DeleteLibraryRoot");
|
||||
|
||||
endpoints.MapPost<IFileLibraryEndpointService>("api/library/roots/scan", (service, ctx) => service.ScanRootAsync(ctx))
|
||||
.WithOpenApi("FileLibrary", "立即扫描文件库目录。")
|
||||
.WithName("ScanLibraryRoot");
|
||||
|
||||
endpoints.MapGet<IFileLibraryEndpointService>("api/files", (service, ctx) => service.SearchFilesAsync(ctx))
|
||||
.WithOpenApi("FileLibrary", "分页查询已扫描文件。")
|
||||
.WithName("SearchFiles");
|
||||
|
||||
endpoints.MapGet<IFileLibraryEndpointService>("api/files/detail", (service, ctx) => service.GetFileAsync(ctx))
|
||||
.WithOpenApi("FileLibrary", "查询文件详情。")
|
||||
.WithName("GetFileDetail");
|
||||
|
||||
endpoints.MapGet<IFileLibraryEndpointService>("api/files/text", (service, ctx) => service.GetTextPreviewAsync(ctx))
|
||||
.WithOpenApi("FileLibrary", "预览文本文件。")
|
||||
.WithName("GetTextPreview");
|
||||
|
||||
// ---- 需要鉴权的端点示例 ----
|
||||
// endpoints.MapGet("api/admin/dashboard", AdminDashboardAsync)
|
||||
// .WithName("AdminDashboard")
|
||||
// .RequireAuthorization = true
|
||||
// .Policy = "AdminOnly";
|
||||
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
#region 业务处理方法
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库查询天气预报(优先数据库,回退到内存生成)。
|
||||
/// </summary>
|
||||
private static async Task<object?> GetWeatherForecastsAsync(ServiceEndpointContext ctx)
|
||||
{
|
||||
var sp = ctx.Items["ServiceProvider"] as IServiceProvider;
|
||||
|
||||
// 尝试从数据库读取
|
||||
if (sp?.GetService(typeof(AppDataContext)) is AppDataContext db)
|
||||
{
|
||||
var dbForecasts = await db.WeatherForecasts
|
||||
.OrderByDescending(f => f.Date)
|
||||
.Take(5)
|
||||
.ToListAsync();
|
||||
|
||||
if (dbForecasts.Count > 0)
|
||||
{
|
||||
return ResponseHelper.Ok(dbForecasts, "获取天气预报成功(来自数据库)");
|
||||
}
|
||||
}
|
||||
|
||||
// 回退:内存生成(数据库为空时)
|
||||
var service = sp?.GetService(typeof(WeatherForecastService)) as WeatherForecastService
|
||||
?? new WeatherForecastService();
|
||||
|
||||
var forecasts = service.GetWeatherForecasts();
|
||||
return ResponseHelper.Ok(forecasts, "获取天气预报成功(内存生成)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库获取用户信息(演示数据库查询),若无数据则返回演示用户。
|
||||
/// </summary>
|
||||
/// <param name="ctx">服务端点上下文。</param>
|
||||
/// <returns>用户信息。</returns>
|
||||
private static async Task<object?> GetUserFromDatabaseAsync(ServiceEndpointContext ctx)
|
||||
{
|
||||
var sp = ctx.Items["ServiceProvider"] as IServiceProvider;
|
||||
|
||||
// 尝试从数据库读取用户
|
||||
if (sp?.GetService(typeof(AppDataContext)) is AppDataContext db)
|
||||
{
|
||||
var users = await db.Set<UserEntity>().Take(1).ToListAsync();
|
||||
if (users.Count > 0)
|
||||
{
|
||||
return ResponseHelper.Ok(users[0], "获取用户成功(来自数据库)");
|
||||
}
|
||||
}
|
||||
|
||||
// 回退:演示数据
|
||||
await Task.Delay(100);
|
||||
var user = new { id = 1, name = "张三", email = "zhangsan@example.com" };
|
||||
return ResponseHelper.Ok(user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理前端发送的数据(POST 演示),将数据存入数据库或转为大写返回。
|
||||
/// </summary>
|
||||
/// <param name="ctx">服务端点上下文。</param>
|
||||
/// <returns>处理结果。</returns>
|
||||
private static async Task<object?> ProcessDataAsync(ServiceEndpointContext ctx)
|
||||
{
|
||||
var sp = ctx.Items["ServiceProvider"] as IServiceProvider;
|
||||
|
||||
// 演示:将收到的数据存入数据库
|
||||
var input = ctx.Body ?? string.Empty;
|
||||
if (sp?.GetService(typeof(AppDataContext)) is AppDataContext db && !string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
var forecast = new WeatherForecastEntity
|
||||
{
|
||||
Date = DateOnly.FromDateTime(DateTime.Now),
|
||||
TemperatureC = 20,
|
||||
Summary = input,
|
||||
};
|
||||
db.WeatherForecasts.Add(forecast);
|
||||
await db.SaveChangesAsync();
|
||||
return ResponseHelper.Ok(forecast, "数据已存入数据库");
|
||||
}
|
||||
|
||||
await Task.Delay(200);
|
||||
return ResponseHelper.Ok(new { input, processed = input.ToUpperInvariant() });
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Avalonia_Services.Services.FileLibrary
|
||||
{
|
||||
public sealed record AddLibraryRootRequest(
|
||||
[property: JsonPropertyName("path")] string? Path,
|
||||
[property: JsonPropertyName("displayName")] string? DisplayName = null,
|
||||
[property: JsonPropertyName("scanIntervalMinutes")] int? ScanIntervalMinutes = null);
|
||||
|
||||
public sealed record UpdateLibraryRootRequest(
|
||||
[property: JsonPropertyName("id")] int Id,
|
||||
[property: JsonPropertyName("isEnabled")] bool IsEnabled);
|
||||
|
||||
public sealed record ScanLibraryRootRequest(
|
||||
[property: JsonPropertyName("id")] int Id);
|
||||
|
||||
public sealed record DeleteLibraryRootRequest(
|
||||
[property: JsonPropertyName("id")] int Id);
|
||||
|
||||
public sealed record DriveDto(
|
||||
string Name,
|
||||
string DisplayName,
|
||||
string RootDirectory,
|
||||
string DriveType,
|
||||
long? TotalSize,
|
||||
long? AvailableFreeSpace,
|
||||
bool IsReady);
|
||||
|
||||
public sealed record DirectoryDto(
|
||||
string Name,
|
||||
string FullPath);
|
||||
|
||||
public sealed record LibraryRootDto(
|
||||
int Id,
|
||||
string Path,
|
||||
string DisplayName,
|
||||
bool IsEnabled,
|
||||
bool IsAvailable,
|
||||
int ScanIntervalMinutes,
|
||||
DateTime? LastScanStartedAt,
|
||||
DateTime? LastScanCompletedAt,
|
||||
string? LastScanError,
|
||||
int FileCount);
|
||||
|
||||
public sealed record FileRecordDto(
|
||||
int Id,
|
||||
int LibraryRootId,
|
||||
string FileName,
|
||||
string RelativePath,
|
||||
string Extension,
|
||||
long SizeBytes,
|
||||
DateTime LastWriteTimeUtc,
|
||||
string MediaType,
|
||||
string ContentType,
|
||||
string StreamUrl,
|
||||
string? TextUrl,
|
||||
bool BrowserPlayable);
|
||||
|
||||
public sealed record TextPreviewDto(
|
||||
int Id,
|
||||
string FileName,
|
||||
string Content,
|
||||
bool Truncated);
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
using Avalonia_Common.Core;
|
||||
using Avalonia_Services.Core;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Avalonia_Services.Services.FileLibrary
|
||||
{
|
||||
public sealed class FileLibraryEndpointService(IFileLibraryService fileLibrary) : IFileLibraryEndpointService
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
};
|
||||
|
||||
public async Task<object?> GetDrivesAsync(ServiceEndpointContext ctx)
|
||||
{
|
||||
return ResponseHelper.Ok(await fileLibrary.GetDrivesAsync());
|
||||
}
|
||||
|
||||
public async Task<object?> GetDirectoriesAsync(ServiceEndpointContext ctx)
|
||||
{
|
||||
var path = ctx.Query.GetValueOrDefault("path");
|
||||
return ResponseHelper.Ok(await fileLibrary.GetDirectoriesAsync(path));
|
||||
}
|
||||
|
||||
public async Task<object?> GetRootsAsync(ServiceEndpointContext ctx)
|
||||
{
|
||||
return ResponseHelper.Ok(await fileLibrary.GetRootsAsync());
|
||||
}
|
||||
|
||||
public async Task<object?> AddRootAsync(ServiceEndpointContext ctx)
|
||||
{
|
||||
var request = ReadBody<AddLibraryRootRequest>(ctx);
|
||||
return ResponseHelper.Ok(await fileLibrary.AddRootAsync(request), "文件库目录已添加并完成扫描。");
|
||||
}
|
||||
|
||||
public async Task<object?> SetRootEnabledAsync(ServiceEndpointContext ctx)
|
||||
{
|
||||
var request = ReadBody<UpdateLibraryRootRequest>(ctx);
|
||||
return ResponseHelper.Ok(await fileLibrary.SetRootEnabledAsync(request), "文件库目录状态已更新。");
|
||||
}
|
||||
|
||||
public async Task<object?> DeleteRootAsync(ServiceEndpointContext ctx)
|
||||
{
|
||||
var request = ReadBody<DeleteLibraryRootRequest>(ctx);
|
||||
await fileLibrary.DeleteRootAsync(request);
|
||||
return ResponseHelper.Succeed("文件库目录已删除。");
|
||||
}
|
||||
|
||||
public async Task<object?> ScanRootAsync(ServiceEndpointContext ctx)
|
||||
{
|
||||
var request = ReadBody<ScanLibraryRootRequest>(ctx);
|
||||
return ResponseHelper.Ok(await fileLibrary.ScanRootAsync(request.Id), "文件库目录扫描完成。");
|
||||
}
|
||||
|
||||
public async Task<object?> SearchFilesAsync(ServiceEndpointContext ctx)
|
||||
{
|
||||
return await fileLibrary.SearchFilesAsync(ctx);
|
||||
}
|
||||
|
||||
public async Task<object?> GetFileAsync(ServiceEndpointContext ctx)
|
||||
{
|
||||
var id = ReadId(ctx);
|
||||
var file = await fileLibrary.GetFileAsync(id);
|
||||
return file is null
|
||||
? ResponseHelper.Failure(404, "文件不存在或尚未扫描入库。")
|
||||
: ResponseHelper.Ok(file);
|
||||
}
|
||||
|
||||
public async Task<object?> GetTextPreviewAsync(ServiceEndpointContext ctx)
|
||||
{
|
||||
var id = ReadId(ctx);
|
||||
var preview = await fileLibrary.GetTextPreviewAsync(id);
|
||||
return preview is null
|
||||
? ResponseHelper.Failure(404, "文本文件不存在或无法预览。")
|
||||
: ResponseHelper.Ok(preview);
|
||||
}
|
||||
|
||||
private static T ReadBody<T>(ServiceEndpointContext ctx)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ctx.Body))
|
||||
{
|
||||
throw new InvalidOperationException("请求体不能为空。");
|
||||
}
|
||||
|
||||
var body = JsonSerializer.Deserialize<T>(ctx.Body, JsonOptions);
|
||||
return body ?? throw new InvalidOperationException("请求体格式错误。");
|
||||
}
|
||||
|
||||
private static int ReadId(ServiceEndpointContext ctx)
|
||||
{
|
||||
if (int.TryParse(ctx.Query.GetValueOrDefault("id"), out var id) && id > 0)
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("id 参数无效。");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,409 +0,0 @@
|
||||
using Avalonia_Common.Core;
|
||||
using Avalonia_EFCore.Database;
|
||||
using Avalonia_EFCore.Models;
|
||||
using Avalonia_Services.Core;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text;
|
||||
|
||||
namespace Avalonia_Services.Services.FileLibrary
|
||||
{
|
||||
public sealed class FileLibraryService(AppDataContext db) : IFileLibraryService
|
||||
{
|
||||
private const int DefaultScanIntervalMinutes = 5;
|
||||
private const int MaxTextPreviewBytes = 1024 * 1024;
|
||||
|
||||
public Task<List<DriveDto>> GetDrivesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var drives = DriveInfo.GetDrives()
|
||||
.Select(drive => new DriveDto(
|
||||
drive.Name,
|
||||
drive.IsReady ? $"{drive.Name} ({drive.VolumeLabel})" : drive.Name,
|
||||
drive.RootDirectory.FullName,
|
||||
drive.DriveType.ToString(),
|
||||
SafeDriveValue(drive, d => d.TotalSize),
|
||||
SafeDriveValue(drive, d => d.AvailableFreeSpace),
|
||||
drive.IsReady))
|
||||
.OrderBy(drive => drive.Name)
|
||||
.ToList();
|
||||
|
||||
return Task.FromResult(drives);
|
||||
}
|
||||
|
||||
public Task<List<DirectoryDto>> GetDirectoriesAsync(string? path, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var normalized = NormalizeExistingDirectory(path);
|
||||
var directories = Directory.EnumerateDirectories(normalized)
|
||||
.Select(directory => new DirectoryInfo(directory))
|
||||
.OrderBy(directory => directory.Name)
|
||||
.Select(directory => new DirectoryDto(directory.Name, directory.FullName))
|
||||
.ToList();
|
||||
|
||||
return Task.FromResult(directories);
|
||||
}
|
||||
|
||||
public async Task<List<LibraryRootDto>> GetRootsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var counts = await db.ManagedFileRecords
|
||||
.Where(file => file.Exists)
|
||||
.GroupBy(file => file.LibraryRootId)
|
||||
.Select(group => new { RootId = group.Key, Count = group.Count() })
|
||||
.ToDictionaryAsync(item => item.RootId, item => item.Count, cancellationToken);
|
||||
|
||||
var roots = await db.ManagedLibraryRoots
|
||||
.OrderBy(root => root.Path)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return roots.Select(root => ToRootDto(root, counts.GetValueOrDefault(root.Id))).ToList();
|
||||
}
|
||||
|
||||
public async Task<LibraryRootDto> AddRootAsync(AddLibraryRootRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var normalized = NormalizeExistingDirectory(request.Path);
|
||||
var existing = await db.ManagedLibraryRoots.FirstOrDefaultAsync(root => root.Path == normalized, cancellationToken);
|
||||
if (existing is not null)
|
||||
{
|
||||
existing.IsEnabled = true;
|
||||
existing.IsAvailable = true;
|
||||
existing.DisplayName = ResolveDisplayName(normalized, request.DisplayName);
|
||||
existing.ScanIntervalMinutes = NormalizeInterval(request.ScanIntervalMinutes);
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
return await ScanRootAsync(existing.Id, cancellationToken);
|
||||
}
|
||||
|
||||
var root = new ManagedLibraryRoot
|
||||
{
|
||||
Path = normalized,
|
||||
DisplayName = ResolveDisplayName(normalized, request.DisplayName),
|
||||
ScanIntervalMinutes = NormalizeInterval(request.ScanIntervalMinutes),
|
||||
IsEnabled = true,
|
||||
IsAvailable = true,
|
||||
};
|
||||
|
||||
db.ManagedLibraryRoots.Add(root);
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return await ScanRootAsync(root.Id, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<LibraryRootDto> SetRootEnabledAsync(UpdateLibraryRootRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var root = await db.ManagedLibraryRoots.FirstOrDefaultAsync(item => item.Id == request.Id, cancellationToken)
|
||||
?? throw new InvalidOperationException("文件库目录不存在。");
|
||||
|
||||
root.IsEnabled = request.IsEnabled;
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
|
||||
var count = await db.ManagedFileRecords.CountAsync(file => file.LibraryRootId == root.Id && file.Exists, cancellationToken);
|
||||
return ToRootDto(root, count);
|
||||
}
|
||||
|
||||
public async Task DeleteRootAsync(DeleteLibraryRootRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var root = await db.ManagedLibraryRoots.FirstOrDefaultAsync(item => item.Id == request.Id, cancellationToken)
|
||||
?? throw new InvalidOperationException("文件库目录不存在。");
|
||||
|
||||
db.ManagedLibraryRoots.Remove(root);
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<LibraryRootDto> ScanRootAsync(int rootId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var root = await db.ManagedLibraryRoots.FirstOrDefaultAsync(item => item.Id == rootId, cancellationToken)
|
||||
?? throw new InvalidOperationException("文件库目录不存在。");
|
||||
|
||||
root.LastScanStartedAt = DateTime.UtcNow;
|
||||
root.LastScanError = null;
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(root.Path))
|
||||
{
|
||||
throw new DirectoryNotFoundException($"目录不存在:{root.Path}");
|
||||
}
|
||||
|
||||
root.IsAvailable = true;
|
||||
root.IsEnabled = true;
|
||||
|
||||
var existing = await db.ManagedFileRecords
|
||||
.Where(file => file.LibraryRootId == root.Id)
|
||||
.ToDictionaryAsync(file => file.AbsolutePath, StringComparer.OrdinalIgnoreCase, cancellationToken);
|
||||
|
||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var path in EnumerateSupportedFiles(root.Path))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var info = new FileInfo(path);
|
||||
if (!info.Exists || !MediaFileTypes.TryGet(info.Extension.ToLowerInvariant(), out var mediaType, out var contentType, out _))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var absolutePath = info.FullName;
|
||||
seen.Add(absolutePath);
|
||||
|
||||
if (!existing.TryGetValue(absolutePath, out var record))
|
||||
{
|
||||
record = new ManagedFileRecord
|
||||
{
|
||||
LibraryRootId = root.Id,
|
||||
AbsolutePath = absolutePath,
|
||||
};
|
||||
db.ManagedFileRecords.Add(record);
|
||||
}
|
||||
|
||||
record.FileName = info.Name;
|
||||
record.RelativePath = Path.GetRelativePath(root.Path, absolutePath);
|
||||
record.Extension = info.Extension.ToLowerInvariant();
|
||||
record.SizeBytes = info.Length;
|
||||
record.LastWriteTimeUtc = info.LastWriteTimeUtc;
|
||||
record.MediaType = mediaType;
|
||||
record.ContentType = contentType;
|
||||
record.Exists = true;
|
||||
record.LastSeenAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
foreach (var stale in existing.Values.Where(file => !seen.Contains(file.AbsolutePath)))
|
||||
{
|
||||
stale.Exists = false;
|
||||
}
|
||||
|
||||
root.LastScanCompletedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
root.IsAvailable = false;
|
||||
root.IsEnabled = false;
|
||||
root.LastScanError = ex.Message;
|
||||
root.LastScanCompletedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(CancellationToken.None);
|
||||
throw;
|
||||
}
|
||||
|
||||
var count = await db.ManagedFileRecords.CountAsync(file => file.LibraryRootId == root.Id && file.Exists, cancellationToken);
|
||||
return ToRootDto(root, count);
|
||||
}
|
||||
|
||||
public async Task ScanDueRootsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var roots = await db.ManagedLibraryRoots
|
||||
.Where(root => root.IsEnabled && root.IsAvailable)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
foreach (var root in roots)
|
||||
{
|
||||
var interval = Math.Max(1, root.ScanIntervalMinutes);
|
||||
var isDue = root.LastScanCompletedAt is null || root.LastScanCompletedAt.Value.AddMinutes(interval) <= now;
|
||||
if (!isDue)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await ScanRootAsync(root.Id, cancellationToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ScanRootAsync records the error on the root. Continue scanning other roots.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<PagedResponse<FileRecordDto>> SearchFilesAsync(ServiceEndpointContext ctx, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var page = ParseInt(ctx.Query.GetValueOrDefault("page"), 1, 1, 100000);
|
||||
var pageSize = ParseInt(ctx.Query.GetValueOrDefault("pageSize"), 24, 1, 100);
|
||||
var mediaType = ctx.Query.GetValueOrDefault("mediaType")?.Trim();
|
||||
var keyword = ctx.Query.GetValueOrDefault("keyword")?.Trim();
|
||||
var rootId = ParseInt(ctx.Query.GetValueOrDefault("rootId"), 0, 0, int.MaxValue);
|
||||
|
||||
var query = db.ManagedFileRecords
|
||||
.AsNoTracking()
|
||||
.Where(file => file.Exists && file.LibraryRoot != null && file.LibraryRoot.IsAvailable);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(mediaType) && !mediaType.Equals("all", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
query = query.Where(file => file.MediaType == mediaType);
|
||||
}
|
||||
|
||||
if (rootId > 0)
|
||||
{
|
||||
query = query.Where(file => file.LibraryRootId == rootId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(keyword))
|
||||
{
|
||||
query = query.Where(file => file.FileName.Contains(keyword) || file.RelativePath.Contains(keyword));
|
||||
}
|
||||
|
||||
var total = await query.CountAsync(cancellationToken);
|
||||
var items = await query
|
||||
.OrderBy(file => file.MediaType)
|
||||
.ThenBy(file => file.RelativePath)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(file => ToFileDto(file))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return PagedResponse<FileRecordDto>.From(items, total, page, pageSize);
|
||||
}
|
||||
|
||||
public async Task<FileRecordDto?> GetFileAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await db.ManagedFileRecords
|
||||
.AsNoTracking()
|
||||
.Where(file => file.Id == id && file.Exists && file.LibraryRoot != null && file.LibraryRoot.IsAvailable)
|
||||
.Select(file => ToFileDto(file))
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<TextPreviewDto?> GetTextPreviewAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var file = await db.ManagedFileRecords
|
||||
.AsNoTracking()
|
||||
.Include(item => item.LibraryRoot)
|
||||
.FirstOrDefaultAsync(item =>
|
||||
item.Id == id
|
||||
&& item.Exists
|
||||
&& item.MediaType == "text"
|
||||
&& item.LibraryRoot != null
|
||||
&& item.LibraryRoot.IsAvailable,
|
||||
cancellationToken);
|
||||
|
||||
if (file is null || !File.Exists(file.AbsolutePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
await using var stream = File.OpenRead(file.AbsolutePath);
|
||||
var limit = (int)Math.Min(stream.Length, MaxTextPreviewBytes);
|
||||
var buffer = new byte[limit];
|
||||
var read = await stream.ReadAsync(buffer.AsMemory(0, limit), cancellationToken);
|
||||
var content = Encoding.UTF8.GetString(buffer, 0, read);
|
||||
return new TextPreviewDto(file.Id, file.FileName, content, stream.Length > MaxTextPreviewBytes);
|
||||
}
|
||||
|
||||
private static IEnumerable<string> EnumerateSupportedFiles(string rootPath)
|
||||
{
|
||||
var pending = new Stack<string>();
|
||||
pending.Push(rootPath);
|
||||
|
||||
while (pending.Count > 0)
|
||||
{
|
||||
var current = pending.Pop();
|
||||
IEnumerable<string> directories;
|
||||
IEnumerable<string> files;
|
||||
|
||||
try
|
||||
{
|
||||
directories = Directory.EnumerateDirectories(current);
|
||||
files = Directory.EnumerateFiles(current);
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var directory in directories)
|
||||
{
|
||||
pending.Push(directory);
|
||||
}
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (MediaFileTypes.TryGet(Path.GetExtension(file), out _, out _, out _))
|
||||
{
|
||||
yield return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizeExistingDirectory(string? path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
throw new InvalidOperationException("目录路径不能为空。");
|
||||
}
|
||||
|
||||
var fullPath = Path.GetFullPath(path.Trim());
|
||||
if (!Directory.Exists(fullPath))
|
||||
{
|
||||
throw new DirectoryNotFoundException($"目录不存在:{fullPath}");
|
||||
}
|
||||
|
||||
return new DirectoryInfo(fullPath).FullName;
|
||||
}
|
||||
|
||||
private static string ResolveDisplayName(string path, string? displayName)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(displayName))
|
||||
{
|
||||
return displayName.Trim();
|
||||
}
|
||||
|
||||
var directory = new DirectoryInfo(path);
|
||||
return string.IsNullOrWhiteSpace(directory.Name) ? directory.FullName : directory.Name;
|
||||
}
|
||||
|
||||
private static int NormalizeInterval(int? interval)
|
||||
{
|
||||
return Math.Clamp(interval ?? DefaultScanIntervalMinutes, 1, 1440);
|
||||
}
|
||||
|
||||
private static long? SafeDriveValue(DriveInfo drive, Func<DriveInfo, long> selector)
|
||||
{
|
||||
try
|
||||
{
|
||||
return drive.IsReady ? selector(drive) : null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static LibraryRootDto ToRootDto(ManagedLibraryRoot root, int fileCount)
|
||||
{
|
||||
return new LibraryRootDto(
|
||||
root.Id,
|
||||
root.Path,
|
||||
root.DisplayName,
|
||||
root.IsEnabled,
|
||||
root.IsAvailable,
|
||||
root.ScanIntervalMinutes,
|
||||
root.LastScanStartedAt,
|
||||
root.LastScanCompletedAt,
|
||||
root.LastScanError,
|
||||
fileCount);
|
||||
}
|
||||
|
||||
private static FileRecordDto ToFileDto(ManagedFileRecord file)
|
||||
{
|
||||
return new FileRecordDto(
|
||||
file.Id,
|
||||
file.LibraryRootId,
|
||||
file.FileName,
|
||||
file.RelativePath,
|
||||
file.Extension,
|
||||
file.SizeBytes,
|
||||
file.LastWriteTimeUtc,
|
||||
file.MediaType,
|
||||
file.ContentType,
|
||||
$"/api/files/{file.Id}/stream",
|
||||
file.MediaType == "text" ? $"/api/files/text?id={file.Id}" : null,
|
||||
MediaFileTypes.IsBrowserPlayable(file.Extension));
|
||||
}
|
||||
|
||||
private static int ParseInt(string? value, int fallback, int min, int max)
|
||||
{
|
||||
return int.TryParse(value, out var parsed)
|
||||
? Math.Clamp(parsed, min, max)
|
||||
: fallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using Avalonia_Services.Core;
|
||||
|
||||
namespace Avalonia_Services.Services.FileLibrary
|
||||
{
|
||||
public interface IFileLibraryEndpointService
|
||||
{
|
||||
Task<object?> GetDrivesAsync(ServiceEndpointContext ctx);
|
||||
|
||||
Task<object?> GetDirectoriesAsync(ServiceEndpointContext ctx);
|
||||
|
||||
Task<object?> GetRootsAsync(ServiceEndpointContext ctx);
|
||||
|
||||
Task<object?> AddRootAsync(ServiceEndpointContext ctx);
|
||||
|
||||
Task<object?> SetRootEnabledAsync(ServiceEndpointContext ctx);
|
||||
|
||||
Task<object?> DeleteRootAsync(ServiceEndpointContext ctx);
|
||||
|
||||
Task<object?> ScanRootAsync(ServiceEndpointContext ctx);
|
||||
|
||||
Task<object?> SearchFilesAsync(ServiceEndpointContext ctx);
|
||||
|
||||
Task<object?> GetFileAsync(ServiceEndpointContext ctx);
|
||||
|
||||
Task<object?> GetTextPreviewAsync(ServiceEndpointContext ctx);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using Avalonia_Common.Core;
|
||||
using Avalonia_Services.Core;
|
||||
|
||||
namespace Avalonia_Services.Services.FileLibrary
|
||||
{
|
||||
public interface IFileLibraryService
|
||||
{
|
||||
Task<List<DriveDto>> GetDrivesAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
Task<List<DirectoryDto>> GetDirectoriesAsync(string? path, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<List<LibraryRootDto>> GetRootsAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
Task<LibraryRootDto> AddRootAsync(AddLibraryRootRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<LibraryRootDto> SetRootEnabledAsync(UpdateLibraryRootRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
Task DeleteRootAsync(DeleteLibraryRootRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<LibraryRootDto> ScanRootAsync(int rootId, CancellationToken cancellationToken = default);
|
||||
|
||||
Task ScanDueRootsAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
Task<PagedResponse<FileRecordDto>> SearchFilesAsync(ServiceEndpointContext ctx, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<FileRecordDto?> GetFileAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<TextPreviewDto?> GetTextPreviewAsync(int id, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
@@ -1,435 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { api, type DirectoryDto, type DriveDto, type FileRecordDto, type LibraryRootDto, type MediaType, type TextPreviewDto } from './api'
|
||||
|
||||
const isAdminPage = computed(() => window.location.pathname.toLowerCase().startsWith('/admin'))
|
||||
const roots = ref<LibraryRootDto[]>([])
|
||||
const drives = ref<DriveDto[]>([])
|
||||
const directories = ref<DirectoryDto[]>([])
|
||||
const files = ref<FileRecordDto[]>([])
|
||||
const selectedFile = ref<FileRecordDto | null>(null)
|
||||
const textPreview = ref<TextPreviewDto | null>(null)
|
||||
const currentPath = ref('')
|
||||
const manualPath = ref('')
|
||||
const keyword = ref('')
|
||||
const mediaType = ref<MediaType>('all')
|
||||
const rootId = ref<number | undefined>()
|
||||
const isBrowsingRoots = ref(true)
|
||||
const page = ref(1)
|
||||
const pageSize = 24
|
||||
const total = ref(0)
|
||||
const loading = ref(false)
|
||||
const scanningId = ref<number | null>(null)
|
||||
const errorMessage = ref('')
|
||||
|
||||
const totalPages = computed(() => Math.max(1, Math.ceil(total.value / pageSize)))
|
||||
const availableRoots = computed(() => roots.value.filter((root) => root.isAvailable))
|
||||
const activeRoots = computed(() => roots.value.filter((root) => root.isEnabled && root.isAvailable))
|
||||
const selectedRoot = computed(() => roots.value.find((root) => root.id === selectedFile.value?.libraryRootId))
|
||||
const totalRootFiles = computed(() => roots.value.reduce((sum, root) => sum + root.fileCount, 0))
|
||||
const selectedMediaUrl = computed(() => selectedFile.value ? api.mediaUrl(selectedFile.value.streamUrl) : '')
|
||||
const clientTitle = computed(() => {
|
||||
if (isBrowsingRoots.value) return '文件库'
|
||||
return rootId.value ? roots.value.find((root) => root.id === rootId.value)?.displayName ?? '文件' : '文件'
|
||||
})
|
||||
|
||||
function formatSize(bytes: number) {
|
||||
if (bytes < 1024) return `${bytes} B`
|
||||
const units = ['KB', 'MB', 'GB', 'TB']
|
||||
let value = bytes / 1024
|
||||
let index = 0
|
||||
while (value >= 1024 && index < units.length - 1) {
|
||||
value /= 1024
|
||||
index += 1
|
||||
}
|
||||
return `${value.toFixed(value >= 10 ? 1 : 2)} ${units[index]}`
|
||||
}
|
||||
|
||||
function formatDate(value: string | null) {
|
||||
if (!value) return '未扫描'
|
||||
return new Date(value).toLocaleString()
|
||||
}
|
||||
|
||||
function setError(error: unknown) {
|
||||
errorMessage.value = error instanceof Error ? error.message : '操作失败'
|
||||
}
|
||||
|
||||
async function loadRoots() {
|
||||
roots.value = await api.getRoots()
|
||||
}
|
||||
|
||||
async function loadDrives() {
|
||||
drives.value = await api.getDrives()
|
||||
}
|
||||
|
||||
async function openDirectory(path: string) {
|
||||
try {
|
||||
errorMessage.value = ''
|
||||
currentPath.value = path
|
||||
manualPath.value = path
|
||||
directories.value = await api.getDirectories(path)
|
||||
} catch (error) {
|
||||
setError(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function addRoot(path = manualPath.value) {
|
||||
try {
|
||||
errorMessage.value = ''
|
||||
loading.value = true
|
||||
await api.addRoot({ path, scanIntervalMinutes: 5 })
|
||||
await Promise.all([loadRoots(), loadFiles()])
|
||||
} catch (error) {
|
||||
setError(error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleRoot(root: LibraryRootDto) {
|
||||
try {
|
||||
errorMessage.value = ''
|
||||
await api.setRootEnabled(root.id, !root.isEnabled)
|
||||
await loadRoots()
|
||||
} catch (error) {
|
||||
setError(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteRoot(root: LibraryRootDto) {
|
||||
try {
|
||||
errorMessage.value = ''
|
||||
await api.deleteRoot(root.id)
|
||||
if (rootId.value === root.id) rootId.value = undefined
|
||||
await Promise.all([loadRoots(), loadFiles()])
|
||||
} catch (error) {
|
||||
setError(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function scanRoot(root: LibraryRootDto) {
|
||||
try {
|
||||
errorMessage.value = ''
|
||||
scanningId.value = root.id
|
||||
await api.scanRoot(root.id)
|
||||
await Promise.all([loadRoots(), loadFiles()])
|
||||
} catch (error) {
|
||||
setError(error)
|
||||
} finally {
|
||||
scanningId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
async function loadFiles(resetPage = false) {
|
||||
try {
|
||||
errorMessage.value = ''
|
||||
if (resetPage) page.value = 1
|
||||
const result = await api.searchFiles({
|
||||
page: page.value,
|
||||
pageSize,
|
||||
mediaType: mediaType.value,
|
||||
keyword: keyword.value,
|
||||
rootId: rootId.value,
|
||||
})
|
||||
files.value = result.items
|
||||
total.value = result.total
|
||||
if (!selectedFile.value || !files.value.some((file) => file.id === selectedFile.value?.id)) {
|
||||
selectedFile.value = null
|
||||
textPreview.value = null
|
||||
}
|
||||
} catch (error) {
|
||||
setError(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function openClientRoot(id: number) {
|
||||
rootId.value = id
|
||||
isBrowsingRoots.value = false
|
||||
await loadFiles(true)
|
||||
}
|
||||
|
||||
function backToRoots() {
|
||||
isBrowsingRoots.value = true
|
||||
rootId.value = undefined
|
||||
selectedFile.value = null
|
||||
textPreview.value = null
|
||||
}
|
||||
|
||||
async function selectFile(file: FileRecordDto | null) {
|
||||
selectedFile.value = file
|
||||
textPreview.value = null
|
||||
if (!file || file.mediaType !== 'text') return
|
||||
|
||||
try {
|
||||
textPreview.value = await api.getTextPreview(file.id)
|
||||
} catch (error) {
|
||||
setError(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function changePage(next: number) {
|
||||
page.value = Math.min(Math.max(1, next), totalPages.value)
|
||||
await loadFiles()
|
||||
}
|
||||
|
||||
async function refreshAll() {
|
||||
await Promise.all([loadRoots(), loadFiles()])
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
await loadRoots()
|
||||
if (isAdminPage.value) {
|
||||
await loadDrives()
|
||||
} else {
|
||||
total.value = activeRoots.value.reduce((sum, root) => sum + root.fileCount, 0)
|
||||
}
|
||||
} catch (error) {
|
||||
setError(error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main v-if="isAdminPage" class="admin-shell">
|
||||
<header class="admin-hero">
|
||||
<div>
|
||||
<p class="eyebrow">FileShare Admin</p>
|
||||
<h1>文件库管理</h1>
|
||||
<p>添加服务器本机磁盘或目录,系统按状态定时扫描,异常目录会自动下线并停止自动扫描。</p>
|
||||
</div>
|
||||
<div class="admin-metrics">
|
||||
<div>
|
||||
<strong>{{ roots.length }}</strong>
|
||||
<span>目录</span>
|
||||
</div>
|
||||
<div>
|
||||
<strong>{{ activeRoots.length }}</strong>
|
||||
<span>正常启用</span>
|
||||
</div>
|
||||
<div>
|
||||
<strong>{{ totalRootFiles }}</strong>
|
||||
<span>入库文件</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<p v-if="errorMessage" class="error-banner">{{ errorMessage }}</p>
|
||||
|
||||
<section class="admin-layout">
|
||||
<section class="admin-card path-card">
|
||||
<div class="card-heading">
|
||||
<div>
|
||||
<h2>添加扫描目录</h2>
|
||||
<p>选择服务器路径,或直接输入绝对路径。</p>
|
||||
</div>
|
||||
<a href="/" class="client-link">客户端</a>
|
||||
</div>
|
||||
|
||||
<label class="field">
|
||||
<span>服务器路径</span>
|
||||
<div class="inline-form">
|
||||
<input v-model="manualPath" type="text" placeholder="例如 D:\Media 或 E:\" />
|
||||
<button class="primary-button" type="button" :disabled="loading || !manualPath" @click="addRoot()">添加并扫描</button>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<div class="admin-browser">
|
||||
<div class="drive-list">
|
||||
<h3>磁盘</h3>
|
||||
<button
|
||||
v-for="drive in drives"
|
||||
:key="drive.name"
|
||||
class="drive-row"
|
||||
type="button"
|
||||
:disabled="!drive.isReady"
|
||||
@click="openDirectory(drive.rootDirectory)"
|
||||
>
|
||||
<span>{{ drive.displayName }}</span>
|
||||
<small>{{ drive.driveType }} · {{ drive.availableFreeSpace !== null ? formatSize(drive.availableFreeSpace) : '不可用' }}</small>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="directory-list">
|
||||
<div class="browser-header">
|
||||
<h3>目录</h3>
|
||||
<button type="button" class="text-button" :disabled="!currentPath" @click="addRoot(currentPath)">添加当前目录</button>
|
||||
</div>
|
||||
<p class="current-path">{{ currentPath || '请选择一个磁盘' }}</p>
|
||||
<button
|
||||
v-for="directory in directories"
|
||||
:key="directory.fullPath"
|
||||
class="directory-row"
|
||||
type="button"
|
||||
@click="openDirectory(directory.fullPath)"
|
||||
>
|
||||
{{ directory.name }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="admin-card roots-card">
|
||||
<div class="card-heading">
|
||||
<div>
|
||||
<h2>目录状态</h2>
|
||||
<p>异常目录不会自动扫描,客户端也无法访问其中的文件;手动扫描成功后恢复正常。</p>
|
||||
</div>
|
||||
<button class="secondary-button" type="button" :disabled="loading" @click="refreshAll">刷新</button>
|
||||
</div>
|
||||
|
||||
<div class="root-table">
|
||||
<article v-for="root in roots" :key="root.id" class="root-item">
|
||||
<div class="root-main">
|
||||
<span :class="['status-pill', root.isAvailable ? 'ok' : 'bad']">{{ root.isAvailable ? '正常' : '异常' }}</span>
|
||||
<div>
|
||||
<strong>{{ root.displayName }}</strong>
|
||||
<p>{{ root.path }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="root-meta">
|
||||
<span>{{ root.fileCount }} 个文件</span>
|
||||
<span>{{ formatDate(root.lastScanCompletedAt) }}</span>
|
||||
<span>{{ root.isEnabled ? '启用' : '停用' }}</span>
|
||||
</div>
|
||||
<p v-if="root.lastScanError" class="root-error">{{ root.lastScanError }}</p>
|
||||
<div class="root-actions">
|
||||
<button type="button" class="secondary-button" @click="scanRoot(root)">
|
||||
{{ scanningId === root.id ? '扫描中' : root.isAvailable ? '立即扫描' : '手动扫描恢复' }}
|
||||
</button>
|
||||
<button type="button" class="secondary-button" :disabled="!root.isAvailable" @click="toggleRoot(root)">
|
||||
{{ root.isEnabled ? '停用' : '启用' }}
|
||||
</button>
|
||||
<button type="button" class="danger-button" @click="deleteRoot(root)">删除</button>
|
||||
</div>
|
||||
</article>
|
||||
<p v-if="roots.length === 0" class="empty-state">还没有添加扫描目录</p>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<main v-else class="client-shell">
|
||||
<header class="mobile-header">
|
||||
<div>
|
||||
<h1>{{ clientTitle }}</h1>
|
||||
<p>{{ isBrowsingRoots ? `${activeRoots.length} 个目录` : `${total} 个文件` }}</p>
|
||||
</div>
|
||||
<div class="mobile-header-actions">
|
||||
<button v-if="!isBrowsingRoots" type="button" class="back-button" @click="backToRoots">返回</button>
|
||||
<a href="/admin" class="admin-link">管理</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<p v-if="errorMessage" class="error-banner">{{ errorMessage }}</p>
|
||||
|
||||
<section v-if="isBrowsingRoots" class="root-picker">
|
||||
<button
|
||||
v-for="root in activeRoots"
|
||||
:key="root.id"
|
||||
class="root-tile"
|
||||
type="button"
|
||||
@click="openClientRoot(root.id)"
|
||||
>
|
||||
<span>{{ root.displayName }}</span>
|
||||
<strong>{{ root.fileCount }}</strong>
|
||||
<small>{{ root.path }}</small>
|
||||
</button>
|
||||
|
||||
<p v-if="activeRoots.length === 0" class="empty-state">暂无可访问目录</p>
|
||||
</section>
|
||||
|
||||
<section v-else class="mobile-filters">
|
||||
<input v-model="keyword" type="search" placeholder="搜索文件" @keyup.enter="loadFiles(true)" />
|
||||
<div class="filter-row">
|
||||
<select v-model="mediaType" @change="loadFiles(true)">
|
||||
<option value="all">全部</option>
|
||||
<option value="video">视频</option>
|
||||
<option value="audio">音频</option>
|
||||
<option value="text">文本</option>
|
||||
</select>
|
||||
<select v-model="rootId" @change="loadFiles(true)">
|
||||
<option v-for="root in availableRoots" :key="root.id" :value="root.id">{{ root.displayName }}</option>
|
||||
</select>
|
||||
<button class="primary-button" type="button" @click="loadFiles(true)">查询</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="!isBrowsingRoots" class="player-panel">
|
||||
<p v-if="!selectedFile" class="empty-state">请选择一个文件</p>
|
||||
|
||||
<template v-else>
|
||||
<div class="player-title">
|
||||
<div>
|
||||
<h2>{{ selectedFile.fileName }}</h2>
|
||||
<p>{{ selectedRoot?.displayName ?? '文件库' }} · {{ selectedFile.relativePath }}</p>
|
||||
</div>
|
||||
<span>{{ selectedFile.extension }}</span>
|
||||
</div>
|
||||
|
||||
<video
|
||||
v-if="selectedFile.mediaType === 'video' && selectedFile.browserPlayable"
|
||||
:key="selectedFile.id"
|
||||
controls
|
||||
playsinline
|
||||
webkit-playsinline
|
||||
preload="metadata"
|
||||
>
|
||||
<source :src="selectedMediaUrl" :type="selectedFile.contentType" />
|
||||
</video>
|
||||
<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>
|
||||
<p v-else class="unsupported">浏览器不支持在线播放此格式。</p>
|
||||
<a
|
||||
v-if="selectedFile.mediaType !== 'text'"
|
||||
class="open-media-link"
|
||||
:href="selectedMediaUrl"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
新窗口打开原视频/音频
|
||||
</a>
|
||||
<p v-if="textPreview?.truncated" class="hint">文本超过 1 MB,已截断显示。</p>
|
||||
</template>
|
||||
</section>
|
||||
|
||||
<section v-if="!isBrowsingRoots" class="mobile-list">
|
||||
<button
|
||||
v-for="file in files"
|
||||
:key="file.id"
|
||||
class="mobile-file"
|
||||
:class="{ active: selectedFile?.id === file.id }"
|
||||
type="button"
|
||||
@click="selectFile(file)"
|
||||
>
|
||||
<span class="type-badge">{{ file.mediaType }}</span>
|
||||
<span>
|
||||
<strong>{{ file.fileName }}</strong>
|
||||
<small>{{ file.relativePath }}</small>
|
||||
<small>
|
||||
{{ formatSize(file.sizeBytes) }} · {{ formatDate(file.lastWriteTimeUtc) }}
|
||||
<template v-if="file.mediaType !== 'text' && !file.browserPlayable"> · 手机可能不支持</template>
|
||||
</small>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<p v-if="files.length === 0" class="empty-state">暂无可查看文件</p>
|
||||
</section>
|
||||
|
||||
<nav v-if="!isBrowsingRoots" class="mobile-pager">
|
||||
<button type="button" :disabled="page <= 1" @click="changePage(page - 1)">上一页</button>
|
||||
<span>{{ page }} / {{ totalPages }}</span>
|
||||
<button type="button" :disabled="page >= totalPages" @click="changePage(page + 1)">下一页</button>
|
||||
</nav>
|
||||
</main>
|
||||
</template>
|
||||
+12
-34
@@ -1,12 +1,11 @@
|
||||
using Avalonia_Common.Core;
|
||||
using Avalonia_EFCore.Database;
|
||||
using Avalonia_EFCore.Models;
|
||||
using Avalonia_Services.Core;
|
||||
using Avalonia_Services.Services.AuthService;
|
||||
using FileShare_Common.Core;
|
||||
using FileShare_EFCore.Database;
|
||||
using FileShare_EFCore.Models;
|
||||
using FileShare_Services.Core;
|
||||
using FileShare_Services.Services.AuthService;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Avalonia_API.Authentication
|
||||
namespace FileShare_API.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// API 鉴权端点服务,实现 <see cref="IApiAuthEndpointService"/>,
|
||||
@@ -17,21 +16,15 @@ namespace Avalonia_API.Authentication
|
||||
JwtTokenService jwtTokenService,
|
||||
RefreshTokenService refreshTokenService) : IApiAuthEndpointService
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 处理用户登录请求。根据账号(邮箱或用户名)查找或创建用户,
|
||||
/// 生成 JWT Access Token 和 Refresh Token 并返回。
|
||||
/// </summary>
|
||||
/// <param name="ctx">服务端点上下文,包含请求体、请求头等信息。</param>
|
||||
/// <returns>包含 AccessToken、RefreshToken 及过期时间的认证响应。</returns>
|
||||
public async Task<object?> LoginAsync(ServiceEndpointContext ctx)
|
||||
public async Task<IApiResponse> LoginAsync(ApiLoginRequest request, ServiceEndpointContext ctx)
|
||||
{
|
||||
var request = Deserialize<ApiLoginRequest>(ctx.Body);
|
||||
if (string.IsNullOrWhiteSpace(request?.Account))
|
||||
if (string.IsNullOrWhiteSpace(request.Account))
|
||||
{
|
||||
ctx.StatusCode = 400;
|
||||
return ResponseHelper.Failure(400, "账号不能为空");
|
||||
@@ -72,11 +65,10 @@ namespace Avalonia_API.Authentication
|
||||
/// </summary>
|
||||
/// <param name="ctx">服务端点上下文,包含请求体中的 RefreshToken。</param>
|
||||
/// <returns>新的 Token 对;若 Refresh Token 无效则返回 401 错误。</returns>
|
||||
public async Task<object?> RefreshAsync(ServiceEndpointContext ctx)
|
||||
public async Task<IApiResponse> RefreshAsync(ApiRefreshTokenRequest request, ServiceEndpointContext ctx)
|
||||
{
|
||||
var request = Deserialize<ApiRefreshTokenRequest>(ctx.Body);
|
||||
var rotated = await refreshTokenService.RotateAsync(
|
||||
request?.RefreshToken,
|
||||
request.RefreshToken,
|
||||
ctx.GetHeader("User-Agent"),
|
||||
GetRemoteIpAddress(ctx));
|
||||
|
||||
@@ -109,26 +101,12 @@ namespace Avalonia_API.Authentication
|
||||
/// </summary>
|
||||
/// <param name="ctx">服务端点上下文,包含请求体中的 RefreshToken。</param>
|
||||
/// <returns>登出成功的响应。</returns>
|
||||
public async Task<object?> LogoutAsync(ServiceEndpointContext ctx)
|
||||
public async Task<IApiResponse> LogoutAsync(ApiLogoutRequest request, ServiceEndpointContext ctx)
|
||||
{
|
||||
var request = Deserialize<ApiLogoutRequest>(ctx.Body);
|
||||
await refreshTokenService.RevokeAsync(request?.RefreshToken);
|
||||
await refreshTokenService.RevokeAsync(request.RefreshToken);
|
||||
return ResponseHelper.Succeed("退出成功");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 JSON 请求体反序列化为指定类型。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">目标类型。</typeparam>
|
||||
/// <param name="body">JSON 请求体字符串,可为空。</param>
|
||||
/// <returns>反序列化后的对象;若 body 为空则返回默认值。</returns>
|
||||
private static T? Deserialize<T>(string? body)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(body)
|
||||
? default
|
||||
: JsonSerializer.Deserialize<T>(body, JsonOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从上下文的 Items 中提取 ASP.NET Core HttpContext,并获取客户端远程 IP 地址。
|
||||
/// </summary>
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
namespace Avalonia_API.Authentication
|
||||
namespace FileShare_API.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// JWT 鉴权配置选项,从 appsettings.json 的 Jwt 节绑定。
|
||||
@@ -8,12 +8,12 @@ namespace Avalonia_API.Authentication
|
||||
/// <summary>
|
||||
/// 获取或设置 Token 签发者。
|
||||
/// </summary>
|
||||
public string Issuer { get; set; } = "Avalonia-API";
|
||||
public string Issuer { get; set; } = "FileShare-API";
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置 Token 受众。
|
||||
/// </summary>
|
||||
public string Audience { get; set; } = "Avalonia-Client";
|
||||
public string Audience { get; set; } = "FileShare-Client";
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置签名密钥(至少 32 字节)。
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
using Avalonia_EFCore.Models;
|
||||
using FileShare_EFCore.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
|
||||
namespace Avalonia_API.Authentication
|
||||
namespace FileShare_API.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// JWT Token 服务,负责创建包含用户声明和角色的 Access Token。
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
using Avalonia_EFCore.Database;
|
||||
using Avalonia_EFCore.Models;
|
||||
using FileShare_EFCore.Database;
|
||||
using FileShare_EFCore.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Avalonia_API.Authentication
|
||||
namespace FileShare_API.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Refresh Token 服务,负责创建、查找、撤销和轮换 Refresh Token,
|
||||
+22
-10
@@ -1,17 +1,18 @@
|
||||
using Avalonia_API.Authentication;
|
||||
using Avalonia_API.Services;
|
||||
using Avalonia_EFCore.Database;
|
||||
using Avalonia_Services.Core;
|
||||
using Avalonia_Services.Endpoints;
|
||||
using Avalonia_Services.Services;
|
||||
using Avalonia_Services.Services.AuthService;
|
||||
using Avalonia_Services.Services.FileLibrary;
|
||||
using FileShare_API.Authentication;
|
||||
using FileShare_API.Services;
|
||||
using FileShare_EFCore.Database;
|
||||
using FileShare_Services.Core;
|
||||
using FileShare_Services.Endpoints;
|
||||
using FileShare_Services.Services;
|
||||
using FileShare_Services.Services.AuthService;
|
||||
using FileShare_Services.Services.FileLibrary;
|
||||
using FileShare_Services.Services.QrCode;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Text;
|
||||
|
||||
namespace Avalonia_API.Configuration
|
||||
namespace FileShare_API.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// API 项目服务配置扩展类,负责注册数据库、鉴权、业务服务和统一端点。
|
||||
@@ -20,7 +21,7 @@ namespace Avalonia_API.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// 注册统一端点及其依赖的服务(含数据库)。
|
||||
/// 所有业务端点定义在 Avalonia-Services/Endpoints/AppEndpoints.cs。
|
||||
/// 所有业务端点定义在 FileShare-Services/Endpoints/AppEndpoints.cs。
|
||||
/// </summary>
|
||||
public static IServiceCollection AddUnifiedApiServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
@@ -39,8 +40,19 @@ namespace Avalonia_API.Configuration
|
||||
|
||||
// ---- 业务服务 ----
|
||||
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<IFileLibraryEndpointService, FileLibraryEndpointService>();
|
||||
services.AddScoped<IFileStreamService, FileStreamService>();
|
||||
services.AddScoped<IQrCodeService, QrCodeService>();
|
||||
services.Configure<FileLibraryScanOptions>(configuration.GetSection(nameof(FileLibraryScanOptions)));
|
||||
services.AddHostedService<FileLibraryScanHostedService>();
|
||||
services.AddDataProtection()
|
||||
.PersistKeysToFileSystem(new DirectoryInfo(Path.Combine(AppContext.BaseDirectory, "data-protection-keys")));
|
||||
@@ -0,0 +1,78 @@
|
||||
using FileShare_Services.Services.FileLibrary;
|
||||
|
||||
namespace FileShare_API.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// API-only raw file stream endpoints used by browser media elements.
|
||||
/// </summary>
|
||||
public static class FileStreamEndpointExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Map the media URL emitted by <see cref="FileRecordDto"/>.
|
||||
/// </summary>
|
||||
public static IEndpointRouteBuilder MapFileStreamEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
app.MapMethods(
|
||||
"/api/files/{id:int}/stream",
|
||||
["GET", "HEAD"],
|
||||
async (int id, IFileStreamService fileStreamService, HttpContext httpContext) =>
|
||||
{
|
||||
var fileResponse = await fileStreamService.GetFileStreamAsync(id);
|
||||
if (fileResponse is null)
|
||||
{
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
var stream = System.IO.File.Open(
|
||||
fileResponse.FilePath,
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
FileShare.ReadWrite);
|
||||
|
||||
httpContext.Response.Headers.ContentDisposition =
|
||||
$"inline; filename=\"{Uri.EscapeDataString(fileResponse.FileName)}\"";
|
||||
httpContext.Response.Headers.AcceptRanges = "bytes";
|
||||
httpContext.Response.Headers.CacheControl = "public, max-age=3600";
|
||||
|
||||
return Results.File(
|
||||
stream,
|
||||
contentType: fileResponse.ContentType,
|
||||
lastModified: fileResponse.LastModified,
|
||||
enableRangeProcessing: true);
|
||||
})
|
||||
.WithName("StreamManagedFileById")
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
-5
@@ -1,14 +1,14 @@
|
||||
using Avalonia_Services.Core;
|
||||
using FileShare_Services.Core;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using AspNetCoreFilterContext = Microsoft.AspNetCore.Http.EndpointFilterInvocationContext;
|
||||
using AspNetCoreFilterDelegate = Microsoft.AspNetCore.Http.EndpointFilterDelegate;
|
||||
// 解决与 ASP.NET Core 同名类型的冲突
|
||||
using UnifiedFilter = Avalonia_Services.Core.IEndpointFilter;
|
||||
using UnifiedFilter = FileShare_Services.Core.IEndpointFilter;
|
||||
|
||||
namespace Avalonia_API.Extensions
|
||||
namespace FileShare_API.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 将 Avalonia-Services 的统一端点映射到 ASP.NET Core Minimal API。
|
||||
/// 将 FileShare-Services 的统一端点映射到 ASP.NET Core Minimal API。
|
||||
/// 支持鉴权、过滤器、中间件的完整 ASP.NET Core 管道。
|
||||
/// </summary>
|
||||
public static class UnifiedEndpointExtensions
|
||||
@@ -81,7 +81,8 @@ namespace Avalonia_API.Extensions
|
||||
routeHandlerBuilder.WithSummary(endpoint.OpenApiSummary);
|
||||
}
|
||||
|
||||
if (endpoint.OpenApiRequestType is not null)
|
||||
if (endpoint.OpenApiRequestType is not null
|
||||
&& endpoint.HttpMethod is "POST" or "PUT")
|
||||
{
|
||||
routeHandlerBuilder.Accepts(endpoint.OpenApiRequestType, "application/json");
|
||||
}
|
||||
@@ -146,6 +147,23 @@ namespace Avalonia_API.Extensions
|
||||
httpContext.Response.Headers[kvp.Key] = kvp.Value;
|
||||
}
|
||||
|
||||
if (result is FileStreamResponse fileResponse)
|
||||
{
|
||||
if (!System.IO.File.Exists(fileResponse.FilePath))
|
||||
return Results.NotFound();
|
||||
|
||||
var stream = System.IO.File.Open(fileResponse.FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
httpContext.Response.Headers.ContentDisposition = $"inline; filename=\"{Uri.EscapeDataString(fileResponse.FileName)}\"";
|
||||
httpContext.Response.Headers.AcceptRanges = "bytes";
|
||||
httpContext.Response.Headers.CacheControl = "public, max-age=3600";
|
||||
|
||||
return Results.File(
|
||||
stream,
|
||||
contentType: fileResponse.ContentType,
|
||||
lastModified: fileResponse.LastModified,
|
||||
enableRangeProcessing: true);
|
||||
}
|
||||
|
||||
return result is not null ? Results.Json(result) : Results.Ok();
|
||||
};
|
||||
}
|
||||
@@ -175,6 +193,14 @@ namespace Avalonia_API.Extensions
|
||||
ctx.Query[query.Key] = query.Value.ToString();
|
||||
}
|
||||
|
||||
foreach (var routeValue in httpContext.Request.RouteValues)
|
||||
{
|
||||
if (routeValue.Value is not null)
|
||||
{
|
||||
ctx.RouteValues[routeValue.Key] = routeValue.Value.ToString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
if (httpContext.Request.ContentLength > 0)
|
||||
{
|
||||
using var reader = new StreamReader(httpContext.Request.Body);
|
||||
@@ -0,0 +1,49 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<RootNamespace>FileShare_API</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="8.1.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FileShare-Services\FileShare-Services.csproj" />
|
||||
<ProjectReference Include="..\FileShare-Common\FileShare-Common.csproj" />
|
||||
<ProjectReference Include="..\FileShare-EFCore\FileShare-EFCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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>
|
||||
|
||||
<Target Name="RestoreFrontendPackages" BeforeTargets="Build" Condition="'$(SkipFrontendBuild)' != 'true' And Exists('..\FileShare-Web-VUE\package.json') And !Exists('..\FileShare-Web-VUE\node_modules')">
|
||||
<Message Importance="high" Text="Restoring FileShare-Web-VUE npm packages..." />
|
||||
<Exec WorkingDirectory="..\FileShare-Web-VUE" Command="npm.cmd install" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildFrontend" BeforeTargets="Build" DependsOnTargets="RestoreFrontendPackages" Condition="'$(SkipFrontendBuild)' != 'true' And Exists('..\FileShare-Web-VUE\package.json')">
|
||||
<Message Importance="high" Text="Building FileShare-Web-VUE into FileShare-API/wwwroot..." />
|
||||
<Exec WorkingDirectory="..\FileShare-Web-VUE" Command="npm.cmd run build-only" />
|
||||
<RemoveDir Directories="wwwroot" />
|
||||
<MakeDir Directories="wwwroot" />
|
||||
<ItemGroup>
|
||||
<FrontendDist Include="..\FileShare-Web-VUE\dist\**\*.*" />
|
||||
</ItemGroup>
|
||||
<Copy SourceFiles="@(FrontendDist)" DestinationFiles="@(FrontendDist->'wwwroot\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="false" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
@@ -1,9 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ActiveDebugProfile>http</ActiveDebugProfile>
|
||||
<NameOfLastUsedPublishProfile>D:\Project\FileShare\FileShare-API\Properties\PublishProfiles\FolderProfile.pubxml</NameOfLastUsedPublishProfile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ActiveDebugProfile>Avalonia-PC</ActiveDebugProfile>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,6 @@
|
||||
@FileShare_API_HostAddress = http://localhost:5206
|
||||
|
||||
GET {{FileShare_API_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
@@ -1,20 +1,22 @@
|
||||
using Avalonia_API.Configuration;
|
||||
using Avalonia_API.Extensions;
|
||||
using Avalonia_Common.Infrastructure;
|
||||
using Avalonia_EFCore.Database;
|
||||
using Avalonia_Services.Core;
|
||||
using FileShare_API.Configuration;
|
||||
using FileShare_API.Extensions;
|
||||
using FileShare_Common.Infrastructure;
|
||||
using FileShare_EFCore.Database;
|
||||
using FileShare_Services.Core;
|
||||
using Serilog;
|
||||
|
||||
// 初始化日志系统
|
||||
Log.Logger = LoggingConfiguration.CreateDefaultLogger(logDir: "logs");
|
||||
Log.Information("Avalonia-API 正在启动...");
|
||||
Log.Information("FileShare-API 正在启动...");
|
||||
|
||||
try
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// 配置 Kestrel 监听所有本机 IP
|
||||
builder.WebHost.UseUrls("http://0.0.0.0:5206", "https://0.0.0.0:7165");
|
||||
// 配置 Kestrel 监听所有本机 IP(端口从 Server 配置节读取)
|
||||
var httpPort = builder.Configuration.GetValue<int>("Server:HttpPort", 5206);
|
||||
var httpsPort = builder.Configuration.GetValue<int>("Server:HttpsPort", 7165);
|
||||
builder.WebHost.UseUrls($"http://0.0.0.0:{httpPort}", $"https://0.0.0.0:{httpsPort}");
|
||||
|
||||
// 使用 Serilog 作为日志提供程序
|
||||
builder.Host.UseSerilog();
|
||||
@@ -30,7 +32,7 @@ try
|
||||
.AllowAnyMethod());
|
||||
});
|
||||
|
||||
// 注册统一端点及业务服务(入口在 Avalonia-Services/Endpoints/AppEndpoints.cs)
|
||||
// 注册统一端点及业务服务(入口在 FileShare-Services/Endpoints/AppEndpoints.cs)
|
||||
builder.Services.AddUnifiedApiServices(builder.Configuration);
|
||||
|
||||
var app = builder.Build();
|
||||
@@ -46,7 +48,7 @@ try
|
||||
app.MapOpenApi();
|
||||
app.UseSwaggerUI(options =>
|
||||
{
|
||||
options.SwaggerEndpoint("/openapi/v1.json", "Avalonia API v1");
|
||||
options.SwaggerEndpoint("/openapi/v1.json", "FileShare API v1");
|
||||
options.RoutePrefix = "swagger";
|
||||
});
|
||||
}
|
||||
@@ -67,7 +69,7 @@ try
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Fatal(ex, "Avalonia-API 启动失败");
|
||||
Log.Fatal(ex, "FileShare-API 启动失败");
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -0,0 +1,62 @@
|
||||
using FileShare_Services.Services.FileLibrary;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace FileShare_API.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件库定时扫描后台服务,按配置间隔检查是否有需要扫描的文件库目录。
|
||||
/// </summary>
|
||||
public sealed class FileLibraryScanHostedService(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
IOptions<FileLibraryScanOptions> options,
|
||||
ILogger<FileLibraryScanHostedService> logger)
|
||||
: BackgroundService
|
||||
{
|
||||
/// <summary>后台服务检查到期扫描任务的轮询间隔。</summary>
|
||||
private readonly TimeSpan _interval = TimeSpan.FromMinutes(Math.Max(1, options.Value.PollingIntervalMinutes));
|
||||
|
||||
/// <summary>
|
||||
/// 启动扫描循环,首次立即执行,之后按配置的轮询间隔重复执行。
|
||||
/// </summary>
|
||||
/// <param name="stoppingToken">应用关闭时触发的取消令牌。</param>
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
logger.LogInformation("文件库定时扫描服务已启动,轮询间隔 {IntervalMinutes} 分钟。", _interval.TotalMinutes);
|
||||
await ScanAsync(stoppingToken);
|
||||
|
||||
using var timer = new PeriodicTimer(_interval);
|
||||
while (await timer.WaitForNextTickAsync(stoppingToken))
|
||||
{
|
||||
await ScanAsync(stoppingToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建临时作用域并调用 <see cref="IFileLibraryService.ScanDueRootsAsync"/> 执行一次到期扫描。
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
private async Task ScanAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var startedAt = DateTime.UtcNow;
|
||||
logger.LogInformation("文件库定时扫描轮询开始。");
|
||||
|
||||
try
|
||||
{
|
||||
await using var scope = scopeFactory.CreateAsyncScope();
|
||||
var scanner = scope.ServiceProvider.GetRequiredService<IFileLibraryService>();
|
||||
await scanner.ScanDueRootsAsync(cancellationToken);
|
||||
logger.LogInformation(
|
||||
"文件库定时扫描轮询完成,耗时 {ElapsedMilliseconds} ms。",
|
||||
(DateTime.UtcNow - startedAt).TotalMilliseconds);
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
logger.LogInformation("文件库定时扫描轮询已取消。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(ex, "文件库定时扫描失败。");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace FileShare_API.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件库定时扫描后台服务配置。
|
||||
/// </summary>
|
||||
public sealed class FileLibraryScanOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 后台服务检查到期文件库目录的轮询间隔分钟数。
|
||||
/// </summary>
|
||||
public int PollingIntervalMinutes { get; set; } = 1;
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,13 @@
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Server": {
|
||||
"HttpPort": 5206,
|
||||
"HttpsPort": 7165
|
||||
},
|
||||
"Jwt": {
|
||||
"Issuer": "Avalonia-API",
|
||||
"Audience": "Avalonia-Client",
|
||||
"Issuer": "FileShare-API",
|
||||
"Audience": "FileShare-Client",
|
||||
"SigningKey": "change-this-development-signing-key-at-least-32-bytes",
|
||||
"AccessTokenMinutes": 60,
|
||||
"RefreshTokenDays": 30
|
||||
@@ -20,5 +24,13 @@
|
||||
"RecreateDatabase": false,
|
||||
"EnableDetailedLog": false,
|
||||
"Timeout": 30
|
||||
},
|
||||
"ThumbnailStorageOptions": {
|
||||
"RootPath": "thumbnails",
|
||||
"FfmpegPath": "tools/ffmpeg/bin/ffmpeg.exe",
|
||||
"FfprobePath": "tools/ffmpeg/bin/ffprobe.exe"
|
||||
},
|
||||
"FileLibraryScanOptions": {
|
||||
"PollingIntervalMinutes": 5
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,25 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Avalonia_Common.Core
|
||||
namespace FileShare_Common.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 统一端点响应契约。
|
||||
/// </summary>
|
||||
public interface IApiResponse
|
||||
{
|
||||
/// <summary>是否成功。</summary>
|
||||
bool Success { get; }
|
||||
|
||||
/// <summary>业务状态码。</summary>
|
||||
int Code { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统一 API 返回格式。
|
||||
/// 所有接口的返回都包装为此格式,确保前端收到一致的数据结构。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">业务数据类型</typeparam>
|
||||
public class ApiResponse<T>
|
||||
public class ApiResponse<T> : IApiResponse
|
||||
{
|
||||
/// <summary>是否成功</summary>
|
||||
[JsonPropertyName("success")]
|
||||
@@ -113,7 +125,7 @@ namespace Avalonia_Common.Core
|
||||
/// <summary>
|
||||
/// 分页返回格式
|
||||
/// </summary>
|
||||
public class PagedResponse<T>
|
||||
public class PagedResponse<T> : IApiResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置操作是否成功。
|
||||
@@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<RootNamespace>Avalonia_Common</RootNamespace>
|
||||
<RootNamespace>FileShare_Common</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Avalonia_Common.Infrastructure
|
||||
namespace FileShare_Common.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// Serilog 日志配置 —— 可在 Avalonia-API 和 Avalonia-PC 中共享。
|
||||
/// Serilog 日志配置 —— 可在 FileShare-API 和 FileShare-PC 中共享。
|
||||
/// </summary>
|
||||
public static class LoggingConfiguration
|
||||
{
|
||||
+27
-4
@@ -1,12 +1,12 @@
|
||||
using Avalonia_EFCore.Models;
|
||||
using FileShare_EFCore.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Avalonia_EFCore.Database
|
||||
namespace FileShare_EFCore.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用数据库上下文 —— 继承自 Avalonia-EFCore 的 AppDbContext。
|
||||
/// 应用数据库上下文 —— 继承自 FileShare-EFCore 的 AppDbContext。
|
||||
/// 所有业务实体在此注册 DbSet。
|
||||
/// 这是 Avalonia-API 和 Avalonia-PC 共用的具体数据上下文。
|
||||
/// 这是 FileShare-API 和 FileShare-PC 共用的具体数据上下文。
|
||||
/// </summary>
|
||||
public class AppDataContext(DatabaseConfiguration dbConfig) : AppDbContext(dbConfig)
|
||||
{
|
||||
@@ -25,6 +25,9 @@ namespace Avalonia_EFCore.Database
|
||||
/// <summary>文件库文件记录数据</summary>
|
||||
public DbSet<ManagedFileRecord> ManagedFileRecords => Set<ManagedFileRecord>();
|
||||
|
||||
/// <summary>文件缩略图映射数据</summary>
|
||||
public DbSet<ManagedThumbnailMap> ManagedThumbnailMaps => Set<ManagedThumbnailMap>();
|
||||
|
||||
/// <summary>
|
||||
/// 配置实体映射,包括主键、索引和属性约束。
|
||||
/// </summary>
|
||||
@@ -66,8 +69,11 @@ namespace Avalonia_EFCore.Database
|
||||
{
|
||||
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.ThumbnailId).HasDatabaseName("idx-managed-file-record-thumbnail-id");
|
||||
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 => e.LastPlayedAt).HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||
entity.HasIndex(e => e.FileCreationTimeUtc).HasDatabaseName("idx-managed-file-record-file-creation-time");
|
||||
entity.Property(e => e.FileName).HasMaxLength(260);
|
||||
entity.Property(e => e.RelativePath).HasMaxLength(1024);
|
||||
entity.Property(e => e.AbsolutePath).HasMaxLength(2048);
|
||||
@@ -78,6 +84,23 @@ namespace Avalonia_EFCore.Database
|
||||
.WithMany(e => e.Files)
|
||||
.HasForeignKey(e => e.LibraryRootId)
|
||||
.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);
|
||||
});
|
||||
}
|
||||
}
|
||||
+22
-6
@@ -1,6 +1,6 @@
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
|
||||
namespace Avalonia_EFCore.Database
|
||||
namespace FileShare_EFCore.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// 设计时 DbContext 工厂,用于 EF Core 迁移工具生成迁移代码。
|
||||
@@ -58,8 +58,18 @@ namespace Avalonia_EFCore.Database
|
||||
=> new(DesignTimeDatabaseConfiguration.Create(args, DatabaseProvider.MySQL));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为设计时工具(dotnet ef migrations)提供数据库连接配置。
|
||||
/// </summary>
|
||||
internal static class DesignTimeDatabaseConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// 解析命令行参数中的 --provider 选项,创建对应的 <see cref="DatabaseConfiguration"/>。
|
||||
/// 未指定提供程序时默认使用 SQLite。
|
||||
/// </summary>
|
||||
/// <param name="args">命令行参数。</param>
|
||||
/// <param name="defaultProvider">未指定时的默认数据库提供程序。</param>
|
||||
/// <returns>对应数据库提供程序的配置。</returns>
|
||||
public static DatabaseConfiguration Create(string[] args, DatabaseProvider defaultProvider = DatabaseProvider.SQLite)
|
||||
{
|
||||
DatabaseProviderRegistry.RegisterDefaults();
|
||||
@@ -67,14 +77,20 @@ namespace Avalonia_EFCore.Database
|
||||
var provider = GetProvider(args) ?? defaultProvider;
|
||||
return provider switch
|
||||
{
|
||||
DatabaseProvider.SQLite => DatabaseConfiguration.ForSQLite("avalonia-api.db"),
|
||||
DatabaseProvider.SqlServer => DatabaseConfiguration.ForSqlServer("(localdb)\\MSSQLLocalDB", "AvaloniaApi"),
|
||||
DatabaseProvider.PostgreSQL => DatabaseConfiguration.ForPostgreSQL("localhost", "avalonia_api", "postgres", "postgres"),
|
||||
DatabaseProvider.MySQL => DatabaseConfiguration.ForMySQL("localhost", "avalonia_api", "root", "root"),
|
||||
_ => DatabaseConfiguration.ForSQLite("avalonia-api.db"),
|
||||
DatabaseProvider.SQLite => DatabaseConfiguration.ForSQLite("fileshare-api.db"),
|
||||
DatabaseProvider.SqlServer => DatabaseConfiguration.ForSqlServer("(localdb)\\MSSQLLocalDB", "FileShareApi"),
|
||||
DatabaseProvider.PostgreSQL => DatabaseConfiguration.ForPostgreSQL("localhost", "fileshare_api", "postgres", "postgres"),
|
||||
DatabaseProvider.MySQL => DatabaseConfiguration.ForMySQL("localhost", "fileshare_api", "root", "root"),
|
||||
_ => DatabaseConfiguration.ForSQLite("fileshare-api.db"),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从命令行参数中解析 --provider 选项的值。
|
||||
/// 支持 <c>--provider sqlite</c> 和 <c>--provider=sqlite</c> 两种格式。
|
||||
/// </summary>
|
||||
/// <param name="args">命令行参数。</param>
|
||||
/// <returns>解析到的数据库提供程序,未指定或无法识别时返回 null。</returns>
|
||||
private static DatabaseProvider? GetProvider(string[] args)
|
||||
{
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
+1
-1
@@ -4,7 +4,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Avalonia_EFCore.Database
|
||||
namespace FileShare_EFCore.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用数据库上下文基类 —— 自动根据 DatabaseConfiguration 选择数据库提供程序。
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Avalonia_EFCore.Database
|
||||
namespace FileShare_EFCore.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// 支持的数据库提供程序类型。
|
||||
+7
-1
@@ -1,7 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Avalonia_EFCore.Database
|
||||
namespace FileShare_EFCore.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据库服务注册扩展 —— 在 Program.cs 中一行配置数据库。
|
||||
@@ -40,6 +40,12 @@ namespace Avalonia_EFCore.Database
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据 <see cref="DatabaseConfiguration.Provider"/> 注册对应具体类型的 <see cref="AppDataContext"/> 实现。
|
||||
/// </summary>
|
||||
/// <param name="services">服务集合。</param>
|
||||
/// <param name="config">数据库配置。</param>
|
||||
/// <exception cref="NotSupportedException">数据库提供程序未注册时抛出。</exception>
|
||||
private static void AddProviderAppDataContext(this IServiceCollection services, DatabaseConfiguration config)
|
||||
{
|
||||
switch (config.Provider)
|
||||
+4
-3
@@ -1,4 +1,4 @@
|
||||
using Avalonia_Common.Infrastructure;
|
||||
using FileShare_Common.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
@@ -9,7 +9,7 @@ using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Avalonia_EFCore.Database
|
||||
namespace FileShare_EFCore.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据库管理器 —— 负责连接测试、自动迁移、种子数据、版本检查。
|
||||
@@ -95,8 +95,9 @@ namespace Avalonia_EFCore.Database
|
||||
{
|
||||
return await _context.Database.CanConnectAsync();
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppLog.Warning("数据库连接测试失败 Provider={Provider} Error={Error}", _config.Provider, ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -1,12 +1,12 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Avalonia_EFCore.Database
|
||||
namespace FileShare_EFCore.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据库提供程序注册表 —— 统一注册所有支持的提供程序配置委托。
|
||||
/// 具体使用哪个提供程序由各宿主项目决定:
|
||||
/// Avalonia-API:从 appsettings.json 的 DatabaseConfiguration 节读取;
|
||||
/// Avalonia-PC :固定使用 SQLite。
|
||||
/// FileShare-API:从 appsettings.json 的 DatabaseConfiguration 节读取;
|
||||
/// FileShare-PC :固定使用 SQLite。
|
||||
/// </summary>
|
||||
public static class DatabaseProviderRegistry
|
||||
{
|
||||
@@ -37,7 +37,7 @@ namespace Avalonia_EFCore.Database
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册所有内置提供程序的默认配置(四个包均已内置在 Avalonia-EFCore 中)。
|
||||
/// 注册所有内置提供程序的默认配置(四个包均已内置在 FileShare-EFCore 中)。
|
||||
/// 注册完成后由调用方根据自身需求选择具体的 <see cref="DatabaseProvider"/>。
|
||||
/// </summary>
|
||||
public static void RegisterDefaults()
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Avalonia_EFCore.Database
|
||||
namespace FileShare_EFCore.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// SQLite 专用 DbContext,用于隔离 SQLite 迁移集。
|
||||
@@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<RootNamespace>Avalonia_EFCore</RootNamespace>
|
||||
<RootNamespace>FileShare_EFCore</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
@@ -22,7 +22,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Avalonia-Common\Avalonia-Common.csproj" />
|
||||
<ProjectReference Include="..\FileShare-Common\FileShare-Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Avalonia_EFCore.Database;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
@@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.MySQL
|
||||
namespace FileShare_EFCore.Migrations.MySQL
|
||||
{
|
||||
[DbContext(typeof(MySqlAppDataContext))]
|
||||
[Migration("20260520082626_AutoMigration_20260520162543")]
|
||||
@@ -22,7 +22,7 @@ namespace Avalonia_EFCore.Migrations.MySQL
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -82,7 +82,7 @@ namespace Avalonia_EFCore.Migrations.MySQL
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.UserEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -127,7 +127,7 @@ namespace Avalonia_EFCore.Migrations.MySQL
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.WeatherForecastEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using MySql.EntityFrameworkCore.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.MySQL
|
||||
namespace FileShare_EFCore.Migrations.MySQL
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AutoMigration_20260520162543 : Migration
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Avalonia_EFCore.Database;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
@@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.MySQL
|
||||
namespace FileShare_EFCore.Migrations.MySQL
|
||||
{
|
||||
[DbContext(typeof(MySqlAppDataContext))]
|
||||
[Migration("20260520083306_AutoMigration_20260520163216")]
|
||||
@@ -22,7 +22,7 @@ namespace Avalonia_EFCore.Migrations.MySQL
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -82,7 +82,7 @@ namespace Avalonia_EFCore.Migrations.MySQL
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.UserEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -133,7 +133,7 @@ namespace Avalonia_EFCore.Migrations.MySQL
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.WeatherForecastEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.MySQL
|
||||
namespace FileShare_EFCore.Migrations.MySQL
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AutoMigration_20260520163216 : Migration
|
||||
+370
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+448
@@ -0,0 +1,448 @@
|
||||
// <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("20260522092342_AddPlaybackPosition")]
|
||||
partial class AddPlaybackPosition
|
||||
{
|
||||
/// <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<double?>("PlaybackPosition")
|
||||
.HasColumnType("double")
|
||||
.HasColumnName("playback-position");
|
||||
|
||||
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,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.MySQL
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPlaybackPosition : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<double>(
|
||||
name: "playback-position",
|
||||
table: "managed-file-record",
|
||||
type: "double",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "playback-position",
|
||||
table: "managed-file-record");
|
||||
}
|
||||
}
|
||||
}
|
||||
+455
@@ -0,0 +1,455 @@
|
||||
// <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("20260523023700_AutoMigration_20260523103531")]
|
||||
partial class AutoMigration_20260523103531
|
||||
{
|
||||
/// <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<DateTime?>("FileCreationTimeUtc")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("file-creation-time-utc");
|
||||
|
||||
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<double?>("PlaybackPosition")
|
||||
.HasColumnType("double")
|
||||
.HasColumnName("playback-position");
|
||||
|
||||
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("FileCreationTimeUtc")
|
||||
.HasDatabaseName("idx-managed-file-record-file-creation-time");
|
||||
|
||||
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,38 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.MySQL
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AutoMigration_20260523103531 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "file-creation-time-utc",
|
||||
table: "managed-file-record",
|
||||
type: "datetime(6)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-file-record-file-creation-time",
|
||||
table: "managed-file-record",
|
||||
column: "file-creation-time-utc");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "idx-managed-file-record-file-creation-time",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "file-creation-time-utc",
|
||||
table: "managed-file-record");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.MySQL
|
||||
{
|
||||
[DbContext(typeof(MySqlAppDataContext))]
|
||||
partial class MySqlAppDataContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(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<DateTime?>("FileCreationTimeUtc")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("file-creation-time-utc");
|
||||
|
||||
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<double?>("PlaybackPosition")
|
||||
.HasColumnType("double")
|
||||
.HasColumnName("playback-position");
|
||||
|
||||
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("FileCreationTimeUtc")
|
||||
.HasDatabaseName("idx-managed-file-record-file-creation-time");
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Avalonia_EFCore.Database;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
@@ -9,7 +9,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.PostgreSQL
|
||||
namespace FileShare_EFCore.Migrations.PostgreSQL
|
||||
{
|
||||
[DbContext(typeof(PostgreSqlAppDataContext))]
|
||||
[Migration("20260520082617_AutoMigration_20260520162543")]
|
||||
@@ -25,7 +25,7 @@ namespace Avalonia_EFCore.Migrations.PostgreSQL
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -87,7 +87,7 @@ namespace Avalonia_EFCore.Migrations.PostgreSQL
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.UserEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -134,7 +134,7 @@ namespace Avalonia_EFCore.Migrations.PostgreSQL
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.WeatherForecastEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.PostgreSQL
|
||||
namespace FileShare_EFCore.Migrations.PostgreSQL
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AutoMigration_20260520162543 : Migration
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Avalonia_EFCore.Database;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
@@ -9,7 +9,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.PostgreSQL
|
||||
namespace FileShare_EFCore.Migrations.PostgreSQL
|
||||
{
|
||||
[DbContext(typeof(PostgreSqlAppDataContext))]
|
||||
[Migration("20260520083254_AutoMigration_20260520163216")]
|
||||
@@ -25,7 +25,7 @@ namespace Avalonia_EFCore.Migrations.PostgreSQL
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -87,7 +87,7 @@ namespace Avalonia_EFCore.Migrations.PostgreSQL
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.UserEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -140,7 +140,7 @@ namespace Avalonia_EFCore.Migrations.PostgreSQL
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.WeatherForecastEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.PostgreSQL
|
||||
namespace FileShare_EFCore.Migrations.PostgreSQL
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AutoMigration_20260520163216 : Migration
|
||||
Generated
+383
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+111
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+463
@@ -0,0 +1,463 @@
|
||||
// <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("20260522092330_AddPlaybackPosition")]
|
||||
partial class AddPlaybackPosition
|
||||
{
|
||||
/// <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<double?>("PlaybackPosition")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("playback-position");
|
||||
|
||||
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,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.PostgreSQL
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPlaybackPosition : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<double>(
|
||||
name: "playback-position",
|
||||
table: "managed-file-record",
|
||||
type: "double precision",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "playback-position",
|
||||
table: "managed-file-record");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+470
@@ -0,0 +1,470 @@
|
||||
// <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("20260523023643_AutoMigration_20260523103531")]
|
||||
partial class AutoMigration_20260523103531
|
||||
{
|
||||
/// <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<DateTime?>("FileCreationTimeUtc")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("file-creation-time-utc");
|
||||
|
||||
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<double?>("PlaybackPosition")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("playback-position");
|
||||
|
||||
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("FileCreationTimeUtc")
|
||||
.HasDatabaseName("idx-managed-file-record-file-creation-time");
|
||||
|
||||
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,38 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.PostgreSQL
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AutoMigration_20260523103531 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "file-creation-time-utc",
|
||||
table: "managed-file-record",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-file-record-file-creation-time",
|
||||
table: "managed-file-record",
|
||||
column: "file-creation-time-utc");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "idx-managed-file-record-file-creation-time",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "file-creation-time-utc",
|
||||
table: "managed-file-record");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,467 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.PostgreSQL
|
||||
{
|
||||
[DbContext(typeof(PostgreSqlAppDataContext))]
|
||||
partial class PostgreSqlAppDataContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(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<DateTime?>("FileCreationTimeUtc")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("file-creation-time-utc");
|
||||
|
||||
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<double?>("PlaybackPosition")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("playback-position");
|
||||
|
||||
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("FileCreationTimeUtc")
|
||||
.HasDatabaseName("idx-managed-file-record-file-creation-time");
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -1,12 +1,12 @@
|
||||
using System;
|
||||
using Avalonia_EFCore.Database;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.SQLite
|
||||
namespace FileShare_EFCore.Migrations.SQLite
|
||||
{
|
||||
[DbContext(typeof(SqliteAppDataContext))]
|
||||
[Migration("20260514000100_InitialCreate")]
|
||||
@@ -18,7 +18,7 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.0");
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.UserEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.HasComment("用户主键")
|
||||
@@ -52,7 +52,7 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.WeatherForecastEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.HasComment("天气预报主键")
|
||||
+1
-1
@@ -4,7 +4,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.SQLite
|
||||
namespace FileShare_EFCore.Migrations.SQLite
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始数据库基线。后续软件版本只追加新的 Migration,不修改已发布 Migration。
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Avalonia_EFCore.Database;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
@@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.SQLite
|
||||
namespace FileShare_EFCore.Migrations.SQLite
|
||||
{
|
||||
[DbContext(typeof(SqliteAppDataContext))]
|
||||
[Migration("20260515072045_AutoMigration_20260515152037")]
|
||||
@@ -20,7 +20,7 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.0");
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.UserEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -65,7 +65,7 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.WeatherForecastEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.SQLite
|
||||
namespace FileShare_EFCore.Migrations.SQLite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AutoMigration_20260515152037 : Migration
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Avalonia_EFCore.Database;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
@@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.SQLite
|
||||
namespace FileShare_EFCore.Migrations.SQLite
|
||||
{
|
||||
[DbContext(typeof(SqliteAppDataContext))]
|
||||
[Migration("20260515085847_AutoMigration_20260515165835")]
|
||||
@@ -20,7 +20,7 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.0");
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -80,7 +80,7 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.UserEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -125,7 +125,7 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.WeatherForecastEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.SQLite
|
||||
namespace FileShare_EFCore.Migrations.SQLite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AutoMigration_20260515165835 : Migration
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Avalonia_EFCore.Database;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
@@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.SQLite
|
||||
namespace FileShare_EFCore.Migrations.SQLite
|
||||
{
|
||||
[DbContext(typeof(SqliteAppDataContext))]
|
||||
[Migration("20260520083230_AutoMigration_20260520163216")]
|
||||
@@ -20,7 +20,7 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.7");
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -80,7 +80,7 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.UserEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -131,7 +131,7 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.WeatherForecastEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.SQLite
|
||||
namespace FileShare_EFCore.Migrations.SQLite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AutoMigration_20260520163216 : Migration
|
||||
+11
-11
@@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Avalonia_EFCore.Database;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
@@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.SQLite
|
||||
namespace FileShare_EFCore.Migrations.SQLite
|
||||
{
|
||||
[DbContext(typeof(SqliteAppDataContext))]
|
||||
[Migration("20260521080213_AddFileLibrary")]
|
||||
@@ -20,7 +20,7 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.7");
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -80,7 +80,7 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.ManagedFileRecord", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -170,7 +170,7 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -237,7 +237,7 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.UserEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -288,7 +288,7 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.WeatherForecastEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -331,9 +331,9 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.ManagedFileRecord", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.HasOne("Avalonia_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("LibraryRootId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -342,7 +342,7 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
b.Navigation("LibraryRoot");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
});
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.SQLite
|
||||
namespace FileShare_EFCore.Migrations.SQLite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddFileLibrary : Migration
|
||||
+31
-12
@@ -1,23 +1,26 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Avalonia_EFCore.Database;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.SQLite
|
||||
namespace FileShare_EFCore.Migrations.SQLite
|
||||
{
|
||||
[DbContext(typeof(SqliteAppDataContext))]
|
||||
partial class SqliteAppDataContextModelSnapshot : ModelSnapshot
|
||||
[Migration("20260522082814_AutoMigration_20260522162758")]
|
||||
partial class AutoMigration_20260522162758
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.7");
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -77,7 +80,7 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.ManagedFileRecord", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -116,6 +119,10 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
.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");
|
||||
@@ -144,10 +151,19 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
.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");
|
||||
|
||||
@@ -155,6 +171,9 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
.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");
|
||||
|
||||
@@ -167,7 +186,7 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -234,7 +253,7 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.UserEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -285,7 +304,7 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.WeatherForecastEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -328,9 +347,9 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.ManagedFileRecord", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.HasOne("Avalonia_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("LibraryRootId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -339,7 +358,7 @@ namespace Avalonia_EFCore.Migrations.SQLite
|
||||
b.Navigation("LibraryRoot");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
});
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+446
@@ -0,0 +1,446 @@
|
||||
// <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("20260522092304_AddPlaybackPosition")]
|
||||
partial class AddPlaybackPosition
|
||||
{
|
||||
/// <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<double?>("PlaybackPosition")
|
||||
.HasColumnType("REAL")
|
||||
.HasColumnName("playback-position");
|
||||
|
||||
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,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.SQLite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPlaybackPosition : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<double>(
|
||||
name: "playback-position",
|
||||
table: "managed-file-record",
|
||||
type: "REAL",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "playback-position",
|
||||
table: "managed-file-record");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+453
@@ -0,0 +1,453 @@
|
||||
// <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("20260523023607_AutoMigration_20260523103531")]
|
||||
partial class AutoMigration_20260523103531
|
||||
{
|
||||
/// <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<DateTime?>("FileCreationTimeUtc")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("file-creation-time-utc");
|
||||
|
||||
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<double?>("PlaybackPosition")
|
||||
.HasColumnType("REAL")
|
||||
.HasColumnName("playback-position");
|
||||
|
||||
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("FileCreationTimeUtc")
|
||||
.HasDatabaseName("idx-managed-file-record-file-creation-time");
|
||||
|
||||
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,38 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.SQLite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AutoMigration_20260523103531 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "file-creation-time-utc",
|
||||
table: "managed-file-record",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-file-record-file-creation-time",
|
||||
table: "managed-file-record",
|
||||
column: "file-creation-time-utc");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "idx-managed-file-record-file-creation-time",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "file-creation-time-utc",
|
||||
table: "managed-file-record");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,450 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.SQLite
|
||||
{
|
||||
[DbContext(typeof(SqliteAppDataContext))]
|
||||
partial class SqliteAppDataContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(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<DateTime?>("FileCreationTimeUtc")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("file-creation-time-utc");
|
||||
|
||||
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<double?>("PlaybackPosition")
|
||||
.HasColumnType("REAL")
|
||||
.HasColumnName("playback-position");
|
||||
|
||||
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("FileCreationTimeUtc")
|
||||
.HasDatabaseName("idx-managed-file-record-file-creation-time");
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Avalonia_EFCore.Database;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
@@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.SqlServer
|
||||
namespace FileShare_EFCore.Migrations.SqlServer
|
||||
{
|
||||
[DbContext(typeof(SqlServerAppDataContext))]
|
||||
[Migration("20260520082607_AutoMigration_20260520162543")]
|
||||
@@ -25,7 +25,7 @@ namespace Avalonia_EFCore.Migrations.SqlServer
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -87,7 +87,7 @@ namespace Avalonia_EFCore.Migrations.SqlServer
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.UserEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -134,7 +134,7 @@ namespace Avalonia_EFCore.Migrations.SqlServer
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.WeatherForecastEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.SqlServer
|
||||
namespace FileShare_EFCore.Migrations.SqlServer
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AutoMigration_20260520162543 : Migration
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Avalonia_EFCore.Database;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
@@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.SqlServer
|
||||
namespace FileShare_EFCore.Migrations.SqlServer
|
||||
{
|
||||
[DbContext(typeof(SqlServerAppDataContext))]
|
||||
[Migration("20260520083242_AutoMigration_20260520163216")]
|
||||
@@ -25,7 +25,7 @@ namespace Avalonia_EFCore.Migrations.SqlServer
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -87,7 +87,7 @@ namespace Avalonia_EFCore.Migrations.SqlServer
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.UserEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -140,7 +140,7 @@ namespace Avalonia_EFCore.Migrations.SqlServer
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Avalonia_EFCore.Models.WeatherForecastEntity", b =>
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Avalonia_EFCore.Migrations.SqlServer
|
||||
namespace FileShare_EFCore.Migrations.SqlServer
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AutoMigration_20260520163216 : Migration
|
||||
Generated
+383
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+463
@@ -0,0 +1,463 @@
|
||||
// <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("20260522092317_AddPlaybackPosition")]
|
||||
partial class AddPlaybackPosition
|
||||
{
|
||||
/// <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<double?>("PlaybackPosition")
|
||||
.HasColumnType("float")
|
||||
.HasColumnName("playback-position");
|
||||
|
||||
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,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.SqlServer
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPlaybackPosition : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<double>(
|
||||
name: "playback-position",
|
||||
table: "managed-file-record",
|
||||
type: "float",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "playback-position",
|
||||
table: "managed-file-record");
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user