diff --git a/src/Shoebox.Web/Data/AppDbContext.cs b/src/Shoebox.Web/Data/AppDbContext.cs index f18d313..01bba49 100644 --- a/src/Shoebox.Web/Data/AppDbContext.cs +++ b/src/Shoebox.Web/Data/AppDbContext.cs @@ -25,6 +25,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity(media => { media.HasIndex(m => new { m.PoolId, m.ContentHash }); + // The gallery's live feed asks each box for what landed after a moment in time, + // over and over; without this every ask reads and sorts the whole box. + media.HasIndex(m => new { m.PoolId, m.UploadedAt }); media.Property(m => m.OriginalFileName).HasMaxLength(260); media.Property(m => m.UploaderName).HasMaxLength(80); }); diff --git a/src/Shoebox.Web/Migrations/20260818231015_MediaUploadedAtIndex.Designer.cs b/src/Shoebox.Web/Migrations/20260818231015_MediaUploadedAtIndex.Designer.cs new file mode 100644 index 0000000..d683c1a --- /dev/null +++ b/src/Shoebox.Web/Migrations/20260818231015_MediaUploadedAtIndex.Designer.cs @@ -0,0 +1,175 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Shoebox.Web.Data; + +#nullable disable + +namespace Shoebox.Web.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260818231015_MediaUploadedAtIndex")] + partial class MediaUploadedAtIndex + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.10"); + + modelBuilder.Entity("Shoebox.Web.Data.Media", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ContentHash") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ContentType") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Extension") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("HasAnimation") + .HasColumnType("INTEGER"); + + b.Property("HasThumbnail") + .HasColumnType("INTEGER"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("TEXT"); + + b.Property("PoolId") + .HasColumnType("TEXT"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("TakenAt") + .HasColumnType("TEXT"); + + b.Property("UploadedAt") + .HasColumnType("TEXT"); + + b.Property("UploaderName") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("TEXT"); + + b.Property("UploaderUid") + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PoolId", "ContentHash"); + + b.HasIndex("PoolId", "UploadedAt"); + + b.ToTable("Media"); + }); + + modelBuilder.Entity("Shoebox.Web.Data.MediaLike", b => + { + b.Property("MediaId") + .HasColumnType("TEXT"); + + b.Property("UploaderUid") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.HasKey("MediaId", "UploaderUid"); + + b.ToTable("Likes"); + }); + + modelBuilder.Entity("Shoebox.Web.Data.Pool", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AdminKey") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("Pools"); + }); + + modelBuilder.Entity("Shoebox.Web.Data.Media", b => + { + b.HasOne("Shoebox.Web.Data.Pool", "Pool") + .WithMany("Media") + .HasForeignKey("PoolId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Pool"); + }); + + modelBuilder.Entity("Shoebox.Web.Data.MediaLike", b => + { + b.HasOne("Shoebox.Web.Data.Media", "Media") + .WithMany() + .HasForeignKey("MediaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Media"); + }); + + modelBuilder.Entity("Shoebox.Web.Data.Pool", b => + { + b.Navigation("Media"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Shoebox.Web/Migrations/20260818231015_MediaUploadedAtIndex.cs b/src/Shoebox.Web/Migrations/20260818231015_MediaUploadedAtIndex.cs new file mode 100644 index 0000000..4c998db --- /dev/null +++ b/src/Shoebox.Web/Migrations/20260818231015_MediaUploadedAtIndex.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Shoebox.Web.Migrations +{ + /// + public partial class MediaUploadedAtIndex : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_Media_PoolId_UploadedAt", + table: "Media", + columns: new[] { "PoolId", "UploadedAt" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Media_PoolId_UploadedAt", + table: "Media"); + } + } +} diff --git a/src/Shoebox.Web/Migrations/AppDbContextModelSnapshot.cs b/src/Shoebox.Web/Migrations/AppDbContextModelSnapshot.cs index 6fe8a42..63ef367 100644 --- a/src/Shoebox.Web/Migrations/AppDbContextModelSnapshot.cs +++ b/src/Shoebox.Web/Migrations/AppDbContextModelSnapshot.cs @@ -79,6 +79,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("PoolId", "ContentHash"); + b.HasIndex("PoolId", "UploadedAt"); + b.ToTable("Media"); }); diff --git a/src/Shoebox.Web/Pages/Pool/Gallery.cshtml b/src/Shoebox.Web/Pages/Pool/Gallery.cshtml index e79ccb9..e7d314d 100644 --- a/src/Shoebox.Web/Pages/Pool/Gallery.cshtml +++ b/src/Shoebox.Web/Pages/Pool/Gallery.cshtml @@ -22,11 +22,8 @@ }

Box code: @Model.Pool.Code - · @Model.Items.Count photo@(Model.Items.Count == 1 ? "" : "s") - @if (Model.Uploaders.Count > 0) - { - · from @Model.Uploaders.Count @(Model.Uploaders.Count == 1 ? "person" : "people") - } + · @Model.Items.Count photo@(Model.Items.Count == 1 ? "" : "s") +

@if (Model.Pool.ExpiresAt.HasValue) { @@ -77,83 +74,28 @@
- @if (Model.Items.Count > 0) - { - Download all photos - @* Phone-only: revealed by gallery.js on touch devices whose browser can - hand image files to the OS share sheet (mobile Safari / Chrome). *@ - - } - else - { - Download all photos - } - @if (Model.HasOthers) - { - Download everyone else's - } - else - { - Download everyone else's - } + + + @* Phone-only: revealed by gallery.js on touch devices whose browser can + hand image files to the OS share sheet (mobile Safari / Chrome). *@ + + +
-@if (Model.Items.Count == 0) -{ -
-

Nothing here yet. You were there. Prove it.

-
-} +