Skip to content

Commit

Permalink
IHS-44 Change tags db schema (#55)
Browse files Browse the repository at this point in the history
* add image tag

* refactoring after split query
  • Loading branch information
adedw authored Mar 9, 2024
1 parent 1cc78d9 commit a2d1bba
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ public void Configure(EntityTypeBuilder<Image> builder)
builder.Property(i => i.ObjectName).HasMaxLength(200);
builder.Property(i => i.Hidden).HasDefaultValue(false);
builder.HasIndex(i => i.Hidden);
builder.Property(i => i.Categories).HasColumnType("varchar(200)[]");
builder.Property(i => i.UserId).HasConversion<UserId.ValueConverter>();

builder.HasMany(i => i.Tags)
.WithOne(it => it.Image)
.HasForeignKey(it => it.ImageId)
.OnDelete(DeleteBehavior.Cascade);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using ImageHosting.Persistence.ValueTypes;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace ImageHosting.Persistence.Entities.Configuration;

public class ImageTagConfiguration : IEntityTypeConfiguration<ImageTag>
{
public void Configure(EntityTypeBuilder<ImageTag> builder)
{
builder.Property(it => it.TagName).HasMaxLength(32);
builder.Property(it => it.ImageId).HasConversion<ImageId.ValueConverter>();
builder.HasKey(it => new { it.ImageId, it.TagName });
}
}
8 changes: 4 additions & 4 deletions ImageHosting.Persistence/Entities/Image.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ public class Image

public UserId UserId { get; set; }

public string ObjectName { get; set; } = null!;
public required string ObjectName { get; set; }
public bool Hidden { get; set; }
public DateTime UploadedAt { get; set; }
public List<string>? Categories { get; set; }
}

public HashSet<ImageTag>? Tags { get; set; }
}
13 changes: 13 additions & 0 deletions ImageHosting.Persistence/Entities/ImageTag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using ImageHosting.Persistence.Entities.Configuration;
using ImageHosting.Persistence.ValueTypes;
using Microsoft.EntityFrameworkCore;

namespace ImageHosting.Persistence.Entities;

[EntityTypeConfiguration(typeof(ImageTagConfiguration))]
public class ImageTag
{
public required Image Image { get; set; }
public ImageId ImageId { get; init; }
public required string TagName { get; init; }
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 51 additions & 0 deletions ImageHosting.Persistence/Migrations/20240309103014_AddImageTag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace ImageHosting.Persistence.Migrations
{
/// <inheritdoc />
public partial class AddImageTag : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Categories",
table: "Images");

migrationBuilder.CreateTable(
name: "ImageTag",
columns: table => new
{
ImageId = table.Column<Guid>(type: "uuid", nullable: false),
TagName = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ImageTag", x => new { x.ImageId, x.TagName });
table.ForeignKey(
name: "FK_ImageTag_Images_ImageId",
column: x => x.ImageId,
principalTable: "Images",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
}

/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ImageTag");

migrationBuilder.AddColumn<List<string>>(
name: "Categories",
table: "Images",
type: "varchar(200)[]",
nullable: true);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using ImageHosting.Persistence.DbContexts;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
Expand Down Expand Up @@ -31,18 +30,14 @@ protected override void BuildModel(ModelBuilder modelBuilder)

b.HasKey("Name");

b.ToTable("ForbiddenCategories", (string)null);
b.ToTable("ForbiddenCategories");
});

modelBuilder.Entity("ImageHosting.Persistence.Entities.Image", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");

b.Property<List<string>>("Categories")
.HasColumnType("varchar(200)[]");

b.Property<bool>("Hidden")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
Expand All @@ -63,7 +58,37 @@ protected override void BuildModel(ModelBuilder modelBuilder)

b.HasIndex("Hidden");

b.ToTable("Images", (string)null);
b.ToTable("Images");
});

modelBuilder.Entity("ImageHosting.Persistence.Entities.ImageTag", b =>
{
b.Property<Guid>("ImageId")
.HasColumnType("uuid");

b.Property<string>("TagName")
.HasMaxLength(32)
.HasColumnType("character varying(32)");

b.HasKey("ImageId", "TagName");

b.ToTable("ImageTag");
});

modelBuilder.Entity("ImageHosting.Persistence.Entities.ImageTag", b =>
{
b.HasOne("ImageHosting.Persistence.Entities.Image", "Image")
.WithMany("Tags")
.HasForeignKey("ImageId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();

b.Navigation("Image");
});

modelBuilder.Entity("ImageHosting.Persistence.Entities.Image", b =>
{
b.Navigation("Tags");
});
#pragma warning restore 612, 618
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Linq;
using ImageHosting.Persistence.Entities;
using ImageHosting.Storage.Features.Images.Models;
using Microsoft.EntityFrameworkCore;

namespace ImageHosting.Storage.Features.Images.Extensions;

Expand All @@ -15,8 +16,8 @@ public static IQueryable<ReadImage> ToReadImages(this IQueryable<Image> queryabl
Name = i.ObjectName,
UploadedAt = i.UploadedAt,
Hidden = i.Hidden,
Categories = i.Categories,
Categories = i.Tags.Select(it => it.TagName).ToList(),

Check warning on line 19 in ImageHosting.Storage/Features/Images/Extensions/QuaryableExtensions.cs

View workflow job for this annotation

GitHub Actions / codecov

Possible null reference argument for parameter 'source' in 'IEnumerable<string> Enumerable.Select<ImageTag, string>(IEnumerable<ImageTag> source, Func<ImageTag, string> selector)'.

Check warning on line 19 in ImageHosting.Storage/Features/Images/Extensions/QuaryableExtensions.cs

View workflow job for this annotation

GitHub Actions / codecov

Possible null reference argument for parameter 'source' in 'IEnumerable<string> Enumerable.Select<ImageTag, string>(IEnumerable<ImageTag> source, Func<ImageTag, string> selector)'.
Asset = new Uri(baseUri, $"api/v1/images/{i.Id}/asset")
});
}).AsSplitQuery();
}
}
23 changes: 7 additions & 16 deletions ImageHosting.Storage/Features/Images/Handlers/UpdateNameHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@
using System.Threading.Tasks;
using ImageHosting.Persistence.DbContexts;
using ImageHosting.Persistence.ValueTypes;
using ImageHosting.Storage.Features.Images.Exceptions;
using ImageHosting.Storage.Features.Images.Models;
using ImageHosting.Storage.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

namespace ImageHosting.Storage.Features.Images.Handlers;

Expand All @@ -16,24 +13,18 @@ public interface IUpdateNameHandler
Task<ReadImage> UpdateAsync(ImageId imageId, string newName, CancellationToken cancellationToken = default);
}

public class UpdateNameHandler(IImageHostingDbContext dbContext, IOptions<ImagesOptions> options) : IUpdateNameHandler
public class UpdateNameHandler(IImageHostingDbContext dbContext, IGetImageHandler getImageHandler) : IUpdateNameHandler
{
private readonly ImagesOptions _options = options.Value;

public async Task<ReadImage> UpdateAsync(ImageId imageId, string newName,
CancellationToken cancellationToken = default)
{
var imageEntity = await dbContext.Images
await dbContext.Images
.Where(i => i.Id == imageId)
.FirstOrDefaultAsync(cancellationToken);
if (imageEntity is null)
{
throw new ImageMetadataNotFoundException(imageId);
}

imageEntity.ObjectName = newName;
await dbContext.SaveChangesAsync(cancellationToken);
.ExecuteUpdateAsync(
calls => calls.SetProperty(i => i.ObjectName, newName),
cancellationToken);

return ReadImage.From(imageEntity, _options.BaseUri);
var readImage = await getImageHandler.GetAsync(imageId, cancellationToken);
return readImage;
}
}
20 changes: 1 addition & 19 deletions ImageHosting.Storage/Features/Images/Models/ReadImage.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using ImageHosting.Persistence.Entities;
using ImageHosting.Persistence.ValueTypes;

namespace ImageHosting.Storage.Features.Images.Models;
Expand All @@ -11,23 +9,7 @@ public class ReadImage
public required ImageId Id { get; init; }
public required string Name { get; init; }
public required DateTime UploadedAt { get; init; }

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public IReadOnlyList<string>? Categories { get; init; }

public required IReadOnlyList<string> Categories { get; init; }
public required bool Hidden { get; init; }
public required Uri Asset { get; init; }

public static ReadImage From(Image i, Uri baseUri)
{
return new ReadImage
{
Id = i.Id,
Name = i.ObjectName,
UploadedAt = i.UploadedAt,
Hidden = i.Hidden,
Categories = i.Categories,
Asset = new Uri(baseUri, $"api/v1/images/{i.Id}/asset")
};
}
}

0 comments on commit a2d1bba

Please sign in to comment.