diff --git a/ImageBase.WebApp/Controllers/ImagesController.cs b/ImageBase.WebApp/Controllers/ImagesController.cs index e7b74b2..354c5f0 100644 --- a/ImageBase.WebApp/Controllers/ImagesController.cs +++ b/ImageBase.WebApp/Controllers/ImagesController.cs @@ -56,6 +56,17 @@ private async Task CurrentUser() return null; } } + [AllowAnonymous] + [HttpGet("[action]")] + public async Task> Search([FromBody] FullTextSeacrhDto fullTextSeacrhDto) + { + ServiceResponse> serviceSearch = await _imagesService.FullTextSearchByImagesAsync(fullTextSeacrhDto); + if (serviceSearch.Success == false) + { + return Forbid(serviceSearch.Message); + } + return Ok(serviceSearch); + } } } diff --git a/ImageBase.WebApp/Data/Dtos/FullTextSeacrhDto.cs b/ImageBase.WebApp/Data/Dtos/FullTextSeacrhDto.cs new file mode 100644 index 0000000..ce6ccb8 --- /dev/null +++ b/ImageBase.WebApp/Data/Dtos/FullTextSeacrhDto.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ImageBase.WebApp.Data.Dtos +{ + public class FullTextSeacrhDto + { + public string SearchQuery { get; set; } + public int Limit { get; set; } + public int Offset { get; set; } + public bool IncludeTitle { get; set; } + public bool IncludeDescription { get; set; } + public bool IncludeKeyWords { get; set; } + + public string ConvertToPostgreFTSWeights() + { + string result=""; + if (IncludeTitle) result += "A"; + if (IncludeKeyWords) result += "B"; + if (IncludeDescription) result += "C"; + return result; + } + } +} diff --git a/ImageBase.WebApp/Migrations/20201204105242_FTSIntegration.cs b/ImageBase.WebApp/Migrations/20201204105242_FTSIntegration.cs index 800c1f5..3676c63 100644 --- a/ImageBase.WebApp/Migrations/20201204105242_FTSIntegration.cs +++ b/ImageBase.WebApp/Migrations/20201204105242_FTSIntegration.cs @@ -97,6 +97,27 @@ ORDER BY weight ASC WHERE images.id = ids.image_id $$ LANGUAGE SQL IMMUTABLE; "); + migrationBuilder.Sql(@"CREATE OR REPLACE FUNCTION remove_ft_index() RETURNS void AS $$ + BEGIN + DROP INDEX image_vector_idx; + END; + $$ LANGUAGE plpgsql;"); + migrationBuilder.Sql(@"CREATE OR REPLACE FUNCTION disable_ft_trigger() RETURNS void AS $$ + BEGIN + ALTER TABLE images DISABLE TRIGGER image_search_vector_update; + END; + $$ LANGUAGE plpgsql;"); + migrationBuilder.Sql(@"CREATE OR REPLACE FUNCTION enable_ft_trigger() RETURNS void AS $$ + BEGIN + ALTER TABLE images ENABLE TRIGGER image_search_vector_update; + END; + $$ LANGUAGE plpgsql;"); + migrationBuilder.Sql(@"CREATE OR REPLACE FUNCTION create_ft_index() RETURNS void AS $$ + BEGIN + CREATE INDEX IF NOT EXISTS image_vector_idx ON images_ft_search + USING RUM (image_vector); + END; + $$ LANGUAGE plpgsql;"); } protected override void Down(MigrationBuilder migrationBuilder) @@ -122,6 +143,10 @@ protected override void Down(MigrationBuilder migrationBuilder) migrationBuilder.Sql(@"DROP FUNCTION images_fts;"); migrationBuilder.Sql(@"DROP FUNCTION setweight;"); migrationBuilder.Sql(@"DROP FUNCTION image_tsvector_trigger;"); + migrationBuilder.Sql(@"DROP FUNCTION create_ft_index;"); + migrationBuilder.Sql(@"DROP FUNCTION remove_ft_index;"); + migrationBuilder.Sql(@"DROP FUNCTION disable_ft_trigger;"); + migrationBuilder.Sql(@"DROP FUNCTION enable_ft_trigger;"); } } } diff --git a/ImageBase.WebApp/Repositories/CatalogRepository.cs b/ImageBase.WebApp/Repositories/CatalogRepository.cs index 1870571..3aea7bc 100644 --- a/ImageBase.WebApp/Repositories/CatalogRepository.cs +++ b/ImageBase.WebApp/Repositories/CatalogRepository.cs @@ -60,7 +60,7 @@ public void DeleteImageFromCatalog(ImageCatalog imageCatalog) _context.ImageCatalogs.Remove(imageCatalog); } - public async Task> GetImagesByCatalogAsync(long id, int offset, int limit) + public async Task> GetImagesByCatalogAsync(long id, int offset, int limit) { IQueryable query = _context.ImageCatalogs.Where(ic => ic.CatalogId == id) .Include(ic => ic.Image) diff --git a/ImageBase.WebApp/Repositories/IImageRepository.cs b/ImageBase.WebApp/Repositories/IImageRepository.cs index dbf1b8d..d3d0033 100644 --- a/ImageBase.WebApp/Repositories/IImageRepository.cs +++ b/ImageBase.WebApp/Repositories/IImageRepository.cs @@ -1,4 +1,5 @@ -using ImageBase.WebApp.Data.Models; +using ImageBase.WebApp.Data.Dtos; +using ImageBase.WebApp.Data.Models; using System; using System.Collections.Generic; using System.Linq; @@ -10,5 +11,6 @@ public interface IImageRepository: IRepository { Task IsImageTitleAlreadyExists(int id, string title); Task HasCatalogWithUserIdAsync(int? id, string userId); + Task> GetImagesBySearchQueryAsync(FullTextSeacrhDto query); } } diff --git a/ImageBase.WebApp/Repositories/ImageRepository.cs b/ImageBase.WebApp/Repositories/ImageRepository.cs index 045c653..be9e9e2 100644 --- a/ImageBase.WebApp/Repositories/ImageRepository.cs +++ b/ImageBase.WebApp/Repositories/ImageRepository.cs @@ -1,4 +1,5 @@ -using ImageBase.WebApp.Data.Models; +using ImageBase.WebApp.Data.Dtos; +using ImageBase.WebApp.Data.Models; using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; @@ -62,5 +63,19 @@ public async Task HasCatalogWithUserIdAsync(int? id, string userId) { return await _context.Catalogs.Where(c => c.Id == id && c.UserId == userId).AnyAsync(); } + + public async Task> GetImagesBySearchQueryAsync(FullTextSeacrhDto query) + { + var weights=query.ConvertToPostgreFTSWeights(); + var images = await _context.Images + .FromSqlInterpolated($@"SELECT * FROM images_fts({query.SearchQuery},{weights},{query.Limit},{query.Offset});").ToListAsync(); + return new PaginationListDto + { + Items = images, + TotalItemsCount = images.Count(), + Limit=query.Limit, + Offset=query.Offset + }; + } } } diff --git a/ImageBase.WebApp/Services/Implementations/ImageService.cs b/ImageBase.WebApp/Services/Implementations/ImageService.cs index 33e4f21..130a1cf 100644 --- a/ImageBase.WebApp/Services/Implementations/ImageService.cs +++ b/ImageBase.WebApp/Services/Implementations/ImageService.cs @@ -43,5 +43,13 @@ public async Task> CreateImageAsync(AddImageDto addIma return serviceResponse; } + + public async Task>> FullTextSearchByImagesAsync(FullTextSeacrhDto queryDto) + { + var serviceResponse = new ServiceResponse>(); + serviceResponse.Data = await _repository.GetImagesBySearchQueryAsync(queryDto); + serviceResponse.Success = true; + return serviceResponse; + } } } diff --git a/ImageBase.WebApp/Services/Interfaces/IImageService.cs b/ImageBase.WebApp/Services/Interfaces/IImageService.cs index de6c056..880fd07 100644 --- a/ImageBase.WebApp/Services/Interfaces/IImageService.cs +++ b/ImageBase.WebApp/Services/Interfaces/IImageService.cs @@ -1,4 +1,5 @@ using ImageBase.WebApp.Data.Dtos; +using ImageBase.WebApp.Data.Models; using System; using System.Collections.Generic; using System.Linq; @@ -9,5 +10,6 @@ namespace ImageBase.WebApp.Services.Interfaces public interface IImageService { Task> CreateImageAsync(AddImageDto addImageDto, string userId = null); + Task>> FullTextSearchByImagesAsync(FullTextSeacrhDto queryDto); } }