summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Media.cs2
-rw-r--r--Migrations/20231020031326_NullableCurrentUploadedFile.Designer.cs490
-rw-r--r--Migrations/20231020031326_NullableCurrentUploadedFile.cs97
-rw-r--r--Migrations/HBContextModelSnapshot.cs13
-rw-r--r--Pages/Component/AclDialog.razor4
-rw-r--r--Pages/Gallery.razor12
-rw-r--r--Pages/ViewMedia.razor4
-rw-r--r--PrincipalProviders/LocalPrincipalProvider.cs8
-rw-r--r--Program.cs1
-rw-r--r--Services/FeedService.cs117
-rw-r--r--Services/MediaService.cs27
-rw-r--r--Services/SecurityService.cs6
-rw-r--r--wwwroot/js/shake.js2
13 files changed, 711 insertions, 72 deletions
diff --git a/Media.cs b/Media.cs
index f8fc311..baa481d 100644
--- a/Media.cs
+++ b/Media.cs
@@ -11,7 +11,7 @@ public class Media : HBObject {
public string? ShortDescription { get; set; }
public string? LongDescription { get; set; }
public virtual OcrData? OcrData { get; set; }
- public virtual UploadedFile CurrentUploadedFile { get; set; }
+ public virtual UploadedFile? CurrentUploadedFile { get; set; }
public virtual List<UploadedFile> UploadedFiles { get; set; } = new();
public bool IsIngest => Tags
diff --git a/Migrations/20231020031326_NullableCurrentUploadedFile.Designer.cs b/Migrations/20231020031326_NullableCurrentUploadedFile.Designer.cs
new file mode 100644
index 0000000..89f933c
--- /dev/null
+++ b/Migrations/20231020031326_NullableCurrentUploadedFile.Designer.cs
@@ -0,0 +1,490 @@
+// <auto-generated />
+using System;
+using HyperBooru;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace HyperBooru.Migrations
+{
+ [DbContext(typeof(HBContext))]
+ [Migration("20231020031326_NullableCurrentUploadedFile")]
+ partial class NullableCurrentUploadedFile
+ {
+ /// <inheritdoc />
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "7.0.12")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("HyperBooru.Acl", b =>
+ {
+ b.Property<int>("AclId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("AclId"));
+
+ b.HasKey("AclId");
+
+ b.ToTable("Acls");
+ });
+
+ modelBuilder.Entity("HyperBooru.AclRule", b =>
+ {
+ b.Property<int>("AclRuleId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("AclRuleId"));
+
+ b.Property<int?>("AclId")
+ .HasColumnType("integer");
+
+ b.Property<int>("Action")
+ .HasColumnType("integer");
+
+ b.Property<long>("Permissions")
+ .HasColumnType("bigint");
+
+ b.Property<byte[]>("Principal")
+ .IsRequired()
+ .HasColumnType("bytea");
+
+ b.HasKey("AclRuleId");
+
+ b.HasIndex("AclId");
+
+ b.ToTable("AclRules");
+ });
+
+ modelBuilder.Entity("HyperBooru.HBObject", b =>
+ {
+ b.Property<int>("ObjectId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ObjectId"));
+
+ b.Property<int?>("AclId")
+ .HasColumnType("integer");
+
+ b.Property<Guid>("Guid")
+ .HasColumnType("uuid");
+
+ b.Property<byte[]>("Owner")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bytea")
+ .HasDefaultValue(new byte[] { 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 });
+
+ b.HasKey("ObjectId");
+
+ b.HasIndex("AclId")
+ .IsUnique();
+
+ b.HasIndex("Guid");
+
+ b.ToTable("Objects", (string)null);
+
+ b.UseTptMappingStrategy();
+ });
+
+ modelBuilder.Entity("HyperBooru.LocalPrincipal", b =>
+ {
+ b.Property<int>("LocalPrincipalId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("LocalPrincipalId"));
+
+ b.Property<string>("Discriminator")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property<byte[]>("Sid")
+ .IsRequired()
+ .HasColumnType("bytea");
+
+ b.HasKey("LocalPrincipalId");
+
+ b.HasIndex("Name");
+
+ b.HasIndex("Sid");
+
+ b.ToTable("SecurityPrincipals", (string)null);
+
+ b.HasDiscriminator<string>("Discriminator").HasValue("LocalPrincipal");
+
+ b.UseTphMappingStrategy();
+ });
+
+ modelBuilder.Entity("HyperBooru.OcrData", b =>
+ {
+ b.Property<int>("OcrDataId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("OcrDataId"));
+
+ b.Property<int>("MediaId")
+ .HasColumnType("integer");
+
+ b.Property<string>("SearchableText")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property<string>("Text")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property<DateTime>("Timestamp")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("OcrDataId");
+
+ b.HasIndex("MediaId")
+ .IsUnique();
+
+ b.ToTable("OcrData");
+ });
+
+ modelBuilder.Entity("LocalGroupLocalPrincipal", b =>
+ {
+ b.Property<int>("LocalPrincipalId")
+ .HasColumnType("integer");
+
+ b.Property<int>("MemberOfLocalPrincipalId")
+ .HasColumnType("integer");
+
+ b.HasKey("LocalPrincipalId", "MemberOfLocalPrincipalId");
+
+ b.HasIndex("MemberOfLocalPrincipalId");
+
+ b.ToTable("SecurityPrincipalMemberships", (string)null);
+ });
+
+ modelBuilder.Entity("TagDefinitionTagDefinition", b =>
+ {
+ b.Property<int>("ImplicitTagsObjectId")
+ .HasColumnType("integer");
+
+ b.Property<int>("TagDefinitionObjectId")
+ .HasColumnType("integer");
+
+ b.HasKey("ImplicitTagsObjectId", "TagDefinitionObjectId");
+
+ b.HasIndex("TagDefinitionObjectId");
+
+ b.ToTable("ImplicitTags", (string)null);
+ });
+
+ modelBuilder.Entity("HyperBooru.Media", b =>
+ {
+ b.HasBaseType("HyperBooru.HBObject");
+
+ b.Property<int?>("CurrentUploadedFileId")
+ .HasColumnType("integer");
+
+ b.Property<string>("LongDescription")
+ .HasColumnType("text");
+
+ b.Property<string>("ShortDescription")
+ .HasColumnType("text");
+
+ b.HasIndex("CurrentUploadedFileId")
+ .IsUnique();
+
+ b.ToTable("Media", (string)null);
+ });
+
+ modelBuilder.Entity("HyperBooru.Tag", b =>
+ {
+ b.HasBaseType("HyperBooru.HBObject");
+
+ b.Property<DateTime>("CreateTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property<int>("TagDefinitionId")
+ .HasColumnType("integer");
+
+ b.Property<int>("TargetObjectId")
+ .HasColumnType("integer");
+
+ b.HasIndex("TagDefinitionId");
+
+ b.HasIndex("TargetObjectId");
+
+ b.ToTable("Tags", (string)null);
+ });
+
+ modelBuilder.Entity("HyperBooru.TagDefinition", b =>
+ {
+ b.HasBaseType("HyperBooru.HBObject");
+
+ b.Property<string>("Alias")
+ .HasColumnType("text");
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property<string>("Namespace")
+ .HasColumnType("text");
+
+ b.Property<int>("Source")
+ .HasColumnType("integer");
+
+ b.ToTable("TagDefinitions", (string)null);
+
+ b.HasData(
+ new
+ {
+ ObjectId = -1,
+ Guid = new Guid("ebdad4f8-455a-4351-8017-1d4854d6fa38"),
+ Owner = new byte[] { 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 },
+ Name = "nsfw",
+ Source = 0
+ },
+ new
+ {
+ ObjectId = -2,
+ Guid = new Guid("ea212801-5bcc-4c0e-814f-fb9d30db58bc"),
+ Owner = new byte[] { 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 },
+ Name = "ingest",
+ Source = 0
+ });
+ });
+
+ modelBuilder.Entity("HyperBooru.UploadedFile", b =>
+ {
+ b.HasBaseType("HyperBooru.HBObject");
+
+ b.Property<string>("Checksum")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property<bool>("ChecksumVerified")
+ .HasColumnType("boolean");
+
+ b.Property<DateTime?>("CreateTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property<string>("Filename")
+ .HasColumnType("text");
+
+ b.Property<int?>("Height")
+ .HasColumnType("integer");
+
+ b.Property<DateTime?>("LastAccessTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property<DateTime?>("LastWriteTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property<long>("Length")
+ .HasColumnType("bigint");
+
+ b.Property<int>("MediaObjectId")
+ .HasColumnType("integer");
+
+ b.Property<string>("MimeType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property<DateTime>("UploadTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property<int?>("Width")
+ .HasColumnType("integer");
+
+ b.HasIndex("MediaObjectId");
+
+ b.ToTable("UploadedFiles", (string)null);
+ });
+
+ modelBuilder.Entity("HyperBooru.LocalGroup", b =>
+ {
+ b.HasBaseType("HyperBooru.LocalPrincipal");
+
+ b.HasDiscriminator().HasValue("LocalGroup");
+ });
+
+ modelBuilder.Entity("HyperBooru.LocalUser", b =>
+ {
+ b.HasBaseType("HyperBooru.LocalPrincipal");
+
+ b.Property<string>("PasswordHash")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasDiscriminator().HasValue("LocalUser");
+
+ b.HasData(
+ new
+ {
+ LocalPrincipalId = -1,
+ Name = "Administrator",
+ Sid = new byte[] { 1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0 },
+ PasswordHash = "P4geAuE2yX/PDRHuJSq74FF5vO782rWz5c0LAQPR8m45DEYAONhu1wYnAn60PSNyjocqEBdnCeKCJfK3sKyuWw=="
+ });
+ });
+
+ modelBuilder.Entity("HyperBooru.AclRule", b =>
+ {
+ b.HasOne("HyperBooru.Acl", null)
+ .WithMany("Rules")
+ .HasForeignKey("AclId");
+ });
+
+ modelBuilder.Entity("HyperBooru.HBObject", b =>
+ {
+ b.HasOne("HyperBooru.Acl", "Acl")
+ .WithOne("Subject")
+ .HasForeignKey("HyperBooru.HBObject", "AclId");
+
+ b.Navigation("Acl");
+ });
+
+ modelBuilder.Entity("HyperBooru.OcrData", b =>
+ {
+ b.HasOne("HyperBooru.Media", "Media")
+ .WithOne("OcrData")
+ .HasForeignKey("HyperBooru.OcrData", "MediaId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Media");
+ });
+
+ modelBuilder.Entity("LocalGroupLocalPrincipal", b =>
+ {
+ b.HasOne("HyperBooru.LocalPrincipal", null)
+ .WithMany()
+ .HasForeignKey("LocalPrincipalId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("HyperBooru.LocalGroup", null)
+ .WithMany()
+ .HasForeignKey("MemberOfLocalPrincipalId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("TagDefinitionTagDefinition", b =>
+ {
+ b.HasOne("HyperBooru.TagDefinition", null)
+ .WithMany()
+ .HasForeignKey("ImplicitTagsObjectId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("HyperBooru.TagDefinition", null)
+ .WithMany()
+ .HasForeignKey("TagDefinitionObjectId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("HyperBooru.Media", b =>
+ {
+ b.HasOne("HyperBooru.UploadedFile", "CurrentUploadedFile")
+ .WithOne()
+ .HasForeignKey("HyperBooru.Media", "CurrentUploadedFileId");
+
+ b.HasOne("HyperBooru.HBObject", null)
+ .WithOne()
+ .HasForeignKey("HyperBooru.Media", "ObjectId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("CurrentUploadedFile");
+ });
+
+ modelBuilder.Entity("HyperBooru.Tag", b =>
+ {
+ b.HasOne("HyperBooru.HBObject", null)
+ .WithOne()
+ .HasForeignKey("HyperBooru.Tag", "ObjectId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("HyperBooru.TagDefinition", "TagDefinition")
+ .WithMany()
+ .HasForeignKey("TagDefinitionId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("HyperBooru.HBObject", "Target")
+ .WithMany("Tags")
+ .HasForeignKey("TargetObjectId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("TagDefinition");
+
+ b.Navigation("Target");
+ });
+
+ modelBuilder.Entity("HyperBooru.TagDefinition", b =>
+ {
+ b.HasOne("HyperBooru.HBObject", null)
+ .WithOne()
+ .HasForeignKey("HyperBooru.TagDefinition", "ObjectId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("HyperBooru.UploadedFile", b =>
+ {
+ b.HasOne("HyperBooru.Media", "Media")
+ .WithMany("UploadedFiles")
+ .HasForeignKey("MediaObjectId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("HyperBooru.HBObject", null)
+ .WithOne()
+ .HasForeignKey("HyperBooru.UploadedFile", "ObjectId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Media");
+ });
+
+ modelBuilder.Entity("HyperBooru.Acl", b =>
+ {
+ b.Navigation("Rules");
+
+ b.Navigation("Subject")
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("HyperBooru.HBObject", b =>
+ {
+ b.Navigation("Tags");
+ });
+
+ modelBuilder.Entity("HyperBooru.Media", b =>
+ {
+ b.Navigation("OcrData");
+
+ b.Navigation("UploadedFiles");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/Migrations/20231020031326_NullableCurrentUploadedFile.cs b/Migrations/20231020031326_NullableCurrentUploadedFile.cs
new file mode 100644
index 0000000..5159d16
--- /dev/null
+++ b/Migrations/20231020031326_NullableCurrentUploadedFile.cs
@@ -0,0 +1,97 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace HyperBooru.Migrations
+{
+ /// <inheritdoc />
+ public partial class NullableCurrentUploadedFile : Migration
+ {
+ /// <inheritdoc />
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropForeignKey(
+ name: "FK_Media_UploadedFiles_CurrentUploadedFileId",
+ table: "Media");
+
+ migrationBuilder.DropColumn(
+ name: "DisplayName",
+ table: "SecurityPrincipals");
+
+ migrationBuilder.AlterColumn<byte[]>(
+ name: "Owner",
+ table: "Objects",
+ type: "bytea",
+ nullable: false,
+ defaultValue: new byte[] { 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 },
+ oldClrType: typeof(byte[]),
+ oldType: "bytea",
+ oldDefaultValue: new byte[] { 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 });
+
+ migrationBuilder.AlterColumn<int>(
+ name: "CurrentUploadedFileId",
+ table: "Media",
+ type: "integer",
+ nullable: true,
+ oldClrType: typeof(int),
+ oldType: "integer");
+
+ migrationBuilder.AddForeignKey(
+ name: "FK_Media_UploadedFiles_CurrentUploadedFileId",
+ table: "Media",
+ column: "CurrentUploadedFileId",
+ principalTable: "UploadedFiles",
+ principalColumn: "ObjectId");
+ }
+
+ /// <inheritdoc />
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropForeignKey(
+ name: "FK_Media_UploadedFiles_CurrentUploadedFileId",
+ table: "Media");
+
+ migrationBuilder.AddColumn<string>(
+ name: "DisplayName",
+ table: "SecurityPrincipals",
+ type: "text",
+ nullable: false,
+ defaultValue: "");
+
+ migrationBuilder.AlterColumn<byte[]>(
+ name: "Owner",
+ table: "Objects",
+ type: "bytea",
+ nullable: false,
+ defaultValue: new byte[] { 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 },
+ oldClrType: typeof(byte[]),
+ oldType: "bytea",
+ oldDefaultValue: new byte[] { 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 });
+
+ migrationBuilder.AlterColumn<int>(
+ name: "CurrentUploadedFileId",
+ table: "Media",
+ type: "integer",
+ nullable: false,
+ defaultValue: 0,
+ oldClrType: typeof(int),
+ oldType: "integer",
+ oldNullable: true);
+
+ migrationBuilder.UpdateData(
+ table: "SecurityPrincipals",
+ keyColumn: "LocalPrincipalId",
+ keyValue: -1,
+ column: "DisplayName",
+ value: "Administrator");
+
+ migrationBuilder.AddForeignKey(
+ name: "FK_Media_UploadedFiles_CurrentUploadedFileId",
+ table: "Media",
+ column: "CurrentUploadedFileId",
+ principalTable: "UploadedFiles",
+ principalColumn: "ObjectId",
+ onDelete: ReferentialAction.Cascade);
+ }
+ }
+}
diff --git a/Migrations/HBContextModelSnapshot.cs b/Migrations/HBContextModelSnapshot.cs
index b03b20c..9341db6 100644
--- a/Migrations/HBContextModelSnapshot.cs
+++ b/Migrations/HBContextModelSnapshot.cs
@@ -17,7 +17,7 @@ namespace HyperBooru.Migrations
{
#pragma warning disable 612, 618
modelBuilder
- .HasAnnotation("ProductVersion", "7.0.11")
+ .HasAnnotation("ProductVersion", "7.0.12")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
@@ -107,10 +107,6 @@ namespace HyperBooru.Migrations
.IsRequired()
.HasColumnType("text");
- b.Property<string>("DisplayName")
- .IsRequired()
- .HasColumnType("text");
-
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
@@ -196,7 +192,7 @@ namespace HyperBooru.Migrations
{
b.HasBaseType("HyperBooru.HBObject");
- b.Property<int>("CurrentUploadedFileId")
+ b.Property<int?>("CurrentUploadedFileId")
.HasColumnType("integer");
b.Property<string>("LongDescription")
@@ -337,7 +333,6 @@ namespace HyperBooru.Migrations
new
{
LocalPrincipalId = -1,
- DisplayName = "Administrator",
Name = "Administrator",
Sid = new byte[] { 1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0 },
PasswordHash = "P4geAuE2yX/PDRHuJSq74FF5vO782rWz5c0LAQPR8m45DEYAONhu1wYnAn60PSNyjocqEBdnCeKCJfK3sKyuWw=="
@@ -405,9 +400,7 @@ namespace HyperBooru.Migrations
{
b.HasOne("HyperBooru.UploadedFile", "CurrentUploadedFile")
.WithOne()
- .HasForeignKey("HyperBooru.Media", "CurrentUploadedFileId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
+ .HasForeignKey("HyperBooru.Media", "CurrentUploadedFileId");
b.HasOne("HyperBooru.HBObject", null)
.WithOne()
diff --git a/Pages/Component/AclDialog.razor b/Pages/Component/AclDialog.razor
index c924b98..476306b 100644
--- a/Pages/Component/AclDialog.razor
+++ b/Pages/Component/AclDialog.razor
@@ -149,7 +149,7 @@
public bool ApplyDisabled =>
GetAclHashCode(obj.Acl!) == lastHashCode ||
- obj.Acl!.Rules.Select(r => r.Principal).Contains(WellKnownSid.NullSid);
+ obj.Acl!.Rules.Select(r => r.Principal).Contains(WellKnownSid.NullSid);
protected override void OnAfterRender(bool firstRender) {
if(subjectSelect is null || ruleToEdit is null)
@@ -188,9 +188,9 @@
private void ApplyAcl() {
if(obj.Acl!.Rules.Count() == 0) {
- obj.Acl = null;
if(!addedAcl)
db.Remove(obj.Acl!);
+ obj.Acl = null;
}
db.SaveChanges();
diff --git a/Pages/Gallery.razor b/Pages/Gallery.razor
index e779e5b..948d135 100644
--- a/Pages/Gallery.razor
+++ b/Pages/Gallery.razor
@@ -2,6 +2,7 @@
@page "/Gallery"
@inject IDbContextFactory<HBContext> dbFactory
@inject ITagService tagService
+@inject IFeedService feedService
@inject ISearchService searchService
@inject IUserService userService
@inject IJSRuntime jsRuntime
@@ -67,6 +68,15 @@
private Media[] queryResult;
private IEnumerator<Media> mediaEnumerator;
+ private FeedOptions feedOptions = new() {
+ SortType = FeedSortType.Chronological,
+ SortOrder = FeedSortOrder.Descending,
+ RandomPosition = false,
+ IncludeProperties = (media) => media
+ .Include(m => m.Tags)
+ .Include(m => m.CurrentUploadedFile)
+ };
+
protected override void OnInitialized() =>
userService.ShowNsfwChanged += ShowNsfwChanged;
@@ -80,6 +90,8 @@
}
private void LoadMedia() {
+ feedService.InitializeFeed(feedOptions);
+
using var db = dbFactory.CreateDbContext();
if(Query is not null) {
diff --git a/Pages/ViewMedia.razor b/Pages/ViewMedia.razor
index 05cf700..af9a8a1 100644
--- a/Pages/ViewMedia.razor
+++ b/Pages/ViewMedia.razor
@@ -18,8 +18,8 @@
<div id="image-container">
<img
src="/media/@(media.Guid)"
- width=@media.CurrentUploadedFile.Width
- height=@media.CurrentUploadedFile.Height
+ width=@media.CurrentUploadedFile!.Width
+ height=@media.CurrentUploadedFile!.Height
onclick="toggleSidebar()"/>
</div>
<div id="metadata">
diff --git a/PrincipalProviders/LocalPrincipalProvider.cs b/PrincipalProviders/LocalPrincipalProvider.cs
index 723ff81..a9c68e9 100644
--- a/PrincipalProviders/LocalPrincipalProvider.cs
+++ b/PrincipalProviders/LocalPrincipalProvider.cs
@@ -12,25 +12,25 @@ public class LocalPrincipalProvider : PrincipalProvider {
public override IPrincipal? GetPrincipal(string name) {
using var db = dbFactory.CreateDbContext();
- return db.Principals.FirstOrDefault(p => p.Name == name);
+ return db.Principals.FirstOrDefault(p => EF.Functions.ILike(p.Name, name));
}
public override IPrincipal[]? SearchPrincipals(string name) {
using var db = dbFactory.CreateDbContext();
return db.Principals
- .Where(p => p.Name.ToLower().Contains(name))
+ .Where(p => EF.Functions.ILike(p.Name, $"%{name}%"))
.Cast<IPrincipal>()
.ToArray();
}
public override IUser? GetUser(string name) {
using var db = dbFactory.CreateDbContext();
- return db.Users.FirstOrDefault(p => p.Name == name);
+ return db.Users.FirstOrDefault(p => EF.Functions.ILike(p.Name, name));
}
public override IGroup? GetGroup(string name) {
using var db = dbFactory.CreateDbContext();
- return db.Groups.FirstOrDefault(p => p.Name == name);
+ return db.Groups.FirstOrDefault(p => EF.Functions.ILike(p.Name, name));
}
public override IGroup[] GetGroups(SecurityIdentifier sid, bool recurse) {
diff --git a/Program.cs b/Program.cs
index 5cc7839..68c7af3 100644
--- a/Program.cs
+++ b/Program.cs
@@ -28,6 +28,7 @@ public class Program {
builder.Services.AddScoped<ITagService, TagService>();
builder.Services.AddScoped<IMediaService, MediaService>();
builder.Services.AddScoped<IUserService, UserService>();
+ builder.Services.AddScoped<IFeedService, FeedService>();
builder.Services.AddHostedService<OcrService>();
var app = builder.Build();
diff --git a/Services/FeedService.cs b/Services/FeedService.cs
index c66b9ee..b664d62 100644
--- a/Services/FeedService.cs
+++ b/Services/FeedService.cs
@@ -1,78 +1,109 @@
using Microsoft.EntityFrameworkCore;
-using System.Data.Common;
+using Microsoft.EntityFrameworkCore.Query;
namespace HyperBooru.Services;
-public enum FeedOrder {
+public enum FeedSortOrder {
+ Ascending,
+ Descending
+}
+
+public enum FeedSortType {
Chronological,
Rating
}
+public record FeedOptions {
+ public FeedSortType SortType { get; set; }
+ public FeedSortOrder SortOrder { get; set; }
+ public bool RandomPosition { get; set; }
+
+ public Func<IQueryable<Media>, IQueryable<Media>>? IncludeProperties { get; set; }
+}
+
public interface IFeedService {
public IEnumerable<Media> Feed { get; }
-
- public void InitializeFeed(
- FeedOrder order = FeedOrder.Chronological,
- bool descending = true,
- bool randomPosition = false);
+ public void InitializeFeed(FeedOptions feedOptions);
+ public Media[] Next(int count);
}
public class FeedService : IFeedService {
- private FeedConfiguration? feedConfig;
+ private const int FeedChunkSize = 50;
+
+ private FeedOptions? feedOptions;
+ private Media? last;
private IDbContextFactory<HBContext> dbFactory;
public FeedService(IDbContextFactory<HBContext> dbFactory) =>
this.dbFactory = dbFactory;
- public void InitializeFeed(
- FeedOrder order,
- bool descending,
- bool randomPosition) {
-
- feedConfig = new() {
- Order = order,
- Descending = descending,
- RandomPosition = randomPosition
- };
+ public void InitializeFeed(FeedOptions feedOptions) {
+ this.feedOptions = feedOptions;
+ last = null;
}
public IEnumerable<Media> Feed {
get {
- if(feedConfig is null)
- throw new InvalidOperationException("Feed must be initialized first");
-
+ last = null;
while(true) {
- var db = dbFactory.CreateDbContext();
+ var media = Next(FeedChunkSize);
+ if(media.Count() == 0)
+ break;
+ foreach(var m in media)
+ yield return m;
+ }
+ }
+ }
- IOrderedQueryable<Media> media;
+ public Media[] Next(int count) =>
+ NextDbChunk(Math.Abs(count), count < 0);
- switch(feedConfig.Order) {
- default:
- case FeedOrder.Chronological:
- if(feedConfig.Descending)
- media = db.Media.OrderByDescending(m => m.ObjectId);
+ private Media[] NextDbChunk(int chunkSize, bool reverse = false) {
+ if(feedOptions is null)
+ throw new InvalidOperationException("Feed must be initialized first");
+
+ while(true) {
+ var db = dbFactory.CreateDbContext();
+
+ IQueryable<Media> media = db.Media;
+
+ if(feedOptions.IncludeProperties is not null)
+ media = feedOptions.IncludeProperties(media);
+
+ var sortOrder = feedOptions.SortOrder;
+ if(reverse)
+ sortOrder = sortOrder == FeedSortOrder.Ascending
+ ? FeedSortOrder.Descending
+ : FeedSortOrder.Ascending;
+
+ if(last is not null) {
+ switch(feedOptions.SortType) {
+ case FeedSortType.Chronological:
+ if(sortOrder == FeedSortOrder.Descending)
+ media = media.Where(m => m.ObjectId < last.ObjectId);
else
- media = db.Media.OrderBy(m => m.ObjectId);
+ media = media.Where(m => m.ObjectId > last.ObjectId);
break;
}
+ media = media.Where(m => m.ObjectId != last.ObjectId);
+ }
- Media[] mediaArray = media.Take(50).ToArray();
-
- db.Dispose();
-
- if(mediaArray.Count() == 0)
+ switch(feedOptions.SortType) {
+ case FeedSortType.Chronological:
+ if(sortOrder == FeedSortOrder.Descending)
+ media = media.OrderByDescending(m => m.ObjectId);
+ else
+ media = media.OrderBy(m => m.ObjectId);
break;
-
- foreach(var m in mediaArray)
- yield return m;
}
- }
- }
- private record FeedConfiguration {
- public FeedOrder Order { get; set; }
- public bool Descending { get; set; }
- public bool RandomPosition { get; set; }
+ Media[] mediaArray = media
+ .Take(chunkSize)
+ .ToArray();
+
+ if(mediaArray.Count() != 0)
+ last = mediaArray.Last();
+ }
}
}
diff --git a/Services/MediaService.cs b/Services/MediaService.cs
index abc026f..a9e744e 100644
--- a/Services/MediaService.cs
+++ b/Services/MediaService.cs
@@ -132,6 +132,7 @@ public class MediaService : IMediaService {
using var magickImage = new MagickImage(fileData);
var media = db.UploadedFiles
+ .Include(uf => uf.Media)
.FirstOrDefault(uf => uf.Checksum == hash)?
.Media;
@@ -154,28 +155,35 @@ public class MediaService : IMediaService {
.First(td => td.Guid == HBObjectGuid.IngestTag);
media = new() {
- CurrentUploadedFile = fileRecord,
- UploadedFiles = new() {
- fileRecord
+ UploadedFiles = new() {
+ fileRecord,
},
Tags = new() {
new() { TagDefinition = ingestTagDef }
}
};
- using var newFile = System.IO.File.Create(GetPath(media));
+ using var newFile = File.Create(GetPath(media));
fileData.Seek(0, SeekOrigin.Begin);
fileData.CopyTo(newFile);
newFile.Flush();
db.Media.Add(media);
+ db.SaveChanges();
+ media.CurrentUploadedFile = fileRecord;
+ db.SaveChanges();
} else {
- media.UploadedFiles.Add(fileRecord);
+ db.Entry(media).Collection(m => m.UploadedFiles).Load();
+ var fileHashes = media.UploadedFiles
+ .Select(uf => GetUploadedFileHash(uf));
+ // Only add the uploaded file record if it contains new information
+ if(!fileHashes.Contains(GetUploadedFileHash(fileRecord)))
+ media.UploadedFiles.Add(fileRecord);
db.Update(media);
+ db.SaveChanges();
}
- db.SaveChanges();
transaction.Commit();
return media;
@@ -305,4 +313,11 @@ public class MediaService : IMediaService {
Directory.CreateDirectory(fileInfo.Directory.FullName);
return fileInfo.FullName;
}
+
+ private int GetUploadedFileHash(UploadedFile uf) => (
+ uf.CreateTime,
+ uf.LastWriteTime,
+ uf.Filename,
+ uf.Length,
+ uf.Checksum).GetHashCode();
}
diff --git a/Services/SecurityService.cs b/Services/SecurityService.cs
index 85513ec..1a0c445 100644
--- a/Services/SecurityService.cs
+++ b/Services/SecurityService.cs
@@ -64,13 +64,13 @@ public class SecurityService : ISecurityService {
principalProvider.SearchPrincipals(name);
public IPrincipal? GetPrincipal(string name) =>
- principalProvider.GetPrincipal(name);
+ principalProvider.GetPrincipal(name.Trim());
public IUser? GetUser(string name) =>
- principalProvider.GetUser(name);
+ principalProvider.GetUser(name.Trim());
public IGroup? GetGroup(string name) =>
- principalProvider.GetGroup(name);
+ principalProvider.GetGroup(name.Trim());
public bool ValidatePassword(IUser user, string password) =>
principalProvider.ValidatePassword(user, password);
diff --git a/wwwroot/js/shake.js b/wwwroot/js/shake.js
index be984ed..776cd33 100644
--- a/wwwroot/js/shake.js
+++ b/wwwroot/js/shake.js
@@ -1,4 +1,4 @@
-function cycleClass(element, className) {
+function cycleClass(element, className) {
element.classList.remove(className);
element.offsetWidth;
element.classList.add(className);