feat(media): content-hash dedup for uploads + media-library endpoint
Uploads previously wrote every file to disk with a fresh GUID name, so the
same image uploaded twice produced two identical files. Now:
- New MediaAsset table records each stored upload (SHA-256 hash, size, type,
url, kind, scope) + migration. Indexed on (CafeId, ContentHash).
- MediaStorageService computes the content hash on upload; if an identical file
already exists for that café it returns the existing URL instead of writing a
duplicate (covers images, videos, 3D models). Dedup lookup/record run via a
scoped DbContext (the service is a singleton) and never block an upload on
failure.
- GET /api/cafes/{cafeId}/media lists the café's library (newest first, optional
?kind=) so the UI can let users pick an existing file instead of re-uploading.
86 API tests pass.
This commit is contained in:
@@ -1308,6 +1308,55 @@ namespace Meezi.Infrastructure.Data.Migrations
|
||||
b.ToTable("LeaveRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Meezi.Core.Entities.MediaAsset", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("CafeId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("ContentHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Kind")
|
||||
.IsRequired()
|
||||
.HasMaxLength(40)
|
||||
.HasColumnType("character varying(40)");
|
||||
|
||||
b.Property<string>("OriginalFileName")
|
||||
.HasMaxLength(260)
|
||||
.HasColumnType("character varying(260)");
|
||||
|
||||
b.Property<long>("SizeBytes")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("character varying(500)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CafeId", "ContentHash");
|
||||
|
||||
b.ToTable("MediaAssets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Meezi.Core.Entities.MenuCategory", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
|
||||
Reference in New Issue
Block a user