diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 55c6a02..89dae1b 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -19,21 +19,26 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x + cache: true + cache-dependency-path: '**/packages.lock.json' - name: Restore dependencies + working-directory: ./tests/ImageHosting.Storage.UnitTests run: dotnet restore - name: Build + working-directory: ./tests/ImageHosting.Storage.UnitTests run: dotnet build --no-restore /p:ContinuousIntegrationBuild=true - name: Test + working-directory: ./tests/ImageHosting.Storage.UnitTests run: dotnet test --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4 with: fail_ci_if_error: true - directory: ImageHosting.Storage.UnitTests + directory: ./tests/ImageHosting.Storage.UnitTests file: coverage.opencover.xml flags: unittests token: ${{ secrets.CODECOV_TOKEN }} diff --git a/docker-compose.yml b/docker-compose.yml index 77db7b9..7f8a574 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,7 @@ services: Kafka__BootstrapServers__0: kafka:9092 Kafka__NewImagesProducer__TopicName: new-images.v1 Minio__Endpoint: minio:9000 - Images__BaseUri: "http://localhost:8080" + Images__PublicUrl: "http://localhost:8080" depends_on: - minio - postgres diff --git a/src/ImageHosting.Storage.Domain/Entities/Image.cs b/src/ImageHosting.Storage.Domain/Entities/Image.cs index 2c871af..54e6f41 100644 --- a/src/ImageHosting.Storage.Domain/Entities/Image.cs +++ b/src/ImageHosting.Storage.Domain/Entities/Image.cs @@ -7,7 +7,7 @@ public class Image public ImageId Id { get; set; } public UserId UserId { get; set; } - + public required string ObjectName { get; set; } public bool Hidden { get; set; } public DateTime UploadedAt { get; set; } diff --git a/src/ImageHosting.Storage.WebApi/Features/Images/Endpoints/Images.cs b/src/ImageHosting.Storage.WebApi/Features/Images/Endpoints/Images.cs index 238c8de..9736a6a 100644 --- a/src/ImageHosting.Storage.WebApi/Features/Images/Endpoints/Images.cs +++ b/src/ImageHosting.Storage.WebApi/Features/Images/Endpoints/Images.cs @@ -79,6 +79,24 @@ public static RouteGroupBuilder MapImagesEndpoints(this IEndpointRouteBuilder ro .ProducesValidationProblem(StatusCodes.Status422UnprocessableEntity) .MapToApiVersion(1); + images.MapPut(pattern: "{id}/hide", async ([FromRoute] ImageId id, [FromServices] ISetHidenessHandler setHidenessHandler, CancellationToken ct) => + { + var readImage = await setHidenessHandler.SetHidenessAsync(id, true, ct); + return TypedResults.Ok(readImage); + }) + .WithName("HideImage") + .WithTags("Images") + .MapToApiVersion(1); + + images.MapPut(pattern: "{id}/unhide", async ([FromRoute] ImageId id, [FromServices] ISetHidenessHandler setHidenessHandler, CancellationToken ct) => + { + var readImage = await setHidenessHandler.SetHidenessAsync(id, false, ct); + return TypedResults.Ok(readImage); + }) + .WithName("UnhideImage") + .WithTags("Images") + .MapToApiVersion(1); + var tags = images.MapGroup(prefix: "{id}/tags"); tags.MapPost(pattern: "", handler: async ([FromRoute] ImageId id, [FromBody] AddTagsCommand addTagsCommand, @@ -102,7 +120,7 @@ public static RouteGroupBuilder MapImagesEndpoints(this IEndpointRouteBuilder ro .WithTags("Tags") .MapToApiVersion(1); - tags.MapGet("", async ([FromRoute] ImageId id, [FromServices] IGetAllImageTagsHandler getAllImageTagsHandler, CancellationToken ct) => + tags.MapGet(pattern: "", async ([FromRoute] ImageId id, [FromServices] IGetAllImageTagsHandler getAllImageTagsHandler, CancellationToken ct) => { var tags = await getAllImageTagsHandler.GetTagsAsync(id, ct); return TypedResults.Ok(tags); diff --git a/src/ImageHosting.Storage.WebApi/Features/Images/Extensions/QuaryableExtensions.cs b/src/ImageHosting.Storage.WebApi/Features/Images/Extensions/QuaryableExtensions.cs index 73375f4..e6bf9dd 100644 --- a/src/ImageHosting.Storage.WebApi/Features/Images/Extensions/QuaryableExtensions.cs +++ b/src/ImageHosting.Storage.WebApi/Features/Images/Extensions/QuaryableExtensions.cs @@ -6,7 +6,7 @@ namespace ImageHosting.Storage.WebApi.Features.Images.Extensions; public static class QueryableExtensions { - public static IQueryable ToReadImages(this IQueryable queryable, Uri baseUri) + public static IQueryable ToReadImages(this IQueryable queryable, Uri publicUrl) { return queryable.Select(i => new ReadImage { @@ -15,7 +15,7 @@ public static IQueryable ToReadImages(this IQueryable queryabl UploadedAt = i.UploadedAt, Hidden = i.Hidden, Categories = i.Tags.Select(it => it.TagName).ToList(), - Asset = new Uri(baseUri, $"api/v1/images/{i.Id}/asset") + Asset = new Uri(publicUrl, $"api/v1/images/{i.Id}/asset") }).AsSplitQuery(); } } \ No newline at end of file diff --git a/src/ImageHosting.Storage.WebApi/Features/Images/Extensions/ServiceCollectionExtensions.cs b/src/ImageHosting.Storage.WebApi/Features/Images/Extensions/ServiceCollectionExtensions.cs index 829457f..578ba3b 100644 --- a/src/ImageHosting.Storage.WebApi/Features/Images/Extensions/ServiceCollectionExtensions.cs +++ b/src/ImageHosting.Storage.WebApi/Features/Images/Extensions/ServiceCollectionExtensions.cs @@ -22,6 +22,7 @@ public static IServiceCollection AddImageServices(this IServiceCollection servic .AddScoped() .AddScoped() .AddScoped() - .AddScoped(); + .AddScoped() + .AddScoped(); } } \ No newline at end of file diff --git a/src/ImageHosting.Storage.WebApi/Features/Images/Handlers/GetImageHandler.cs b/src/ImageHosting.Storage.WebApi/Features/Images/Handlers/GetImageHandler.cs index ec447ee..640efaf 100644 --- a/src/ImageHosting.Storage.WebApi/Features/Images/Handlers/GetImageHandler.cs +++ b/src/ImageHosting.Storage.WebApi/Features/Images/Handlers/GetImageHandler.cs @@ -1,6 +1,5 @@ using ImageHosting.Storage.Domain.ValueTypes; using ImageHosting.Storage.Infrastructure.DbContexts; -using ImageHosting.Storage.WebApi.Features.Images.Exceptions; using ImageHosting.Storage.WebApi.Features.Images.Extensions; using ImageHosting.Storage.WebApi.Features.Images.Models; using ImageHosting.Storage.WebApi.Options; @@ -11,25 +10,19 @@ namespace ImageHosting.Storage.WebApi.Features.Images.Handlers; public interface IGetImageHandler { - Task GetAsync(ImageId id, CancellationToken cancellationToken = default); + Task GetAsync(ImageId id, CancellationToken cancellationToken = default); } public class GetImageHandler(IImageHostingDbContext dbContext, IOptions options) : IGetImageHandler { - private readonly ImagesOptions _options = options.Value; + private readonly Uri _publicUrl = options.Value.PublicUrl; - public async Task GetAsync(ImageId id, CancellationToken cancellationToken = default) + public Task GetAsync(ImageId id, CancellationToken cancellationToken = default) { - var image = await dbContext.Images + return dbContext.Images .AsNoTracking() .Where(i => i.Id == id) - .ToReadImages(_options.BaseUri) + .ToReadImages(_publicUrl) .FirstOrDefaultAsync(cancellationToken); - if (image is null) - { - throw new ImageMetadataNotFoundException(id); - } - - return image; } } \ No newline at end of file diff --git a/src/ImageHosting.Storage.WebApi/Features/Images/Handlers/SetHidenessHandler.cs b/src/ImageHosting.Storage.WebApi/Features/Images/Handlers/SetHidenessHandler.cs new file mode 100644 index 0000000..99958bc --- /dev/null +++ b/src/ImageHosting.Storage.WebApi/Features/Images/Handlers/SetHidenessHandler.cs @@ -0,0 +1,24 @@ +using ImageHosting.Storage.Domain.ValueTypes; +using ImageHosting.Storage.Infrastructure.DbContexts; +using ImageHosting.Storage.WebApi.Features.Images.Models; +using Microsoft.EntityFrameworkCore; + +namespace ImageHosting.Storage.WebApi.Features.Images.Handlers; + +public interface ISetHidenessHandler +{ + Task SetHidenessAsync(ImageId imageId, bool hidden, CancellationToken ct = default); +} + +public class SetHidenessHandler(IImageHostingDbContext dbContext, IGetImageHandler getImageHandler) : ISetHidenessHandler +{ + public async Task SetHidenessAsync(ImageId imageId, bool hidden, CancellationToken ct = default) + { + await dbContext.Images + .Where(i => i.Id == imageId) + .ExecuteUpdateAsync(calls => calls.SetProperty(i => i.Hidden, hidden), ct); + + var readImage = await getImageHandler.GetAsync(imageId, ct); + return readImage; + } +} diff --git a/src/ImageHosting.Storage.WebApi/Options/ImagesOptions.cs b/src/ImageHosting.Storage.WebApi/Options/ImagesOptions.cs index 8af3622..92629d1 100644 --- a/src/ImageHosting.Storage.WebApi/Options/ImagesOptions.cs +++ b/src/ImageHosting.Storage.WebApi/Options/ImagesOptions.cs @@ -6,5 +6,5 @@ public class ImagesOptions { public const string SectionName = "Images"; - [Required] public required Uri BaseUri { get; init; } + [Required] public required Uri PublicUrl { get; init; } } \ No newline at end of file diff --git a/src/ImageHosting.Storage.WebApi/appsettings.Development.json b/src/ImageHosting.Storage.WebApi/appsettings.Development.json index 3bd20ff..29c3c2a 100644 --- a/src/ImageHosting.Storage.WebApi/appsettings.Development.json +++ b/src/ImageHosting.Storage.WebApi/appsettings.Development.json @@ -34,6 +34,6 @@ } }, "Images": { - "BaseUri": "http://localhost:8080" + "PublicUrl": "http://localhost:8080" } }