diff options
16 files changed, 370 insertions, 70 deletions
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/BaseTimelineTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/BaseTimelineTest.cs index 0bf3b2b2..006b5128 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/BaseTimelineTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/BaseTimelineTest.cs @@ -15,7 +15,7 @@ namespace Timeline.Tests.IntegratedTests for (int i = 0; i <= 3; i++)
{
using var client = await CreateClientAs(i);
- await client.TestPostAsync("timelines", new TimelineCreateRequest { Name = $"t{i}" });
+ await client.TestPostAsync("timelines", new HttpTimelineCreateRequest { Name = $"t{i}" });
}
}
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/BookmarkTimelineTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/BookmarkTimelineTest.cs index 99cf6d3a..23d67d44 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/BookmarkTimelineTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/BookmarkTimelineTest.cs @@ -34,7 +34,7 @@ namespace Timeline.Tests.IntegratedTests public async Task ShouldWork()
{
using var client = await CreateClientAsUser();
- await client.TestPostAsync("timelines", new TimelineCreateRequest { Name = "t1" });
+ await client.TestPostAsync("timelines", new HttpTimelineCreateRequest { Name = "t1" });
{
@@ -88,7 +88,7 @@ namespace Timeline.Tests.IntegratedTests public async Task TimelineGet_IsBookmarkField_ShouldWork()
{
using var client = await CreateClientAsUser();
- await client.TestPostAsync("timelines", new TimelineCreateRequest { Name = "t" });
+ await client.TestPostAsync("timelines", new HttpTimelineCreateRequest { Name = "t" });
{
var t = await client.TestGetAsync<HttpTimeline>("timelines/t");
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/HighlightTimelineTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/HighlightTimelineTest.cs index 440759f4..cfc41468 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/HighlightTimelineTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/HighlightTimelineTest.cs @@ -35,7 +35,7 @@ namespace Timeline.Tests.IntegratedTests {
{
using var client1 = await CreateClientAsUser();
- await client1.TestPostAsync("timelines", new TimelineCreateRequest { Name = "t1" });
+ await client1.TestPostAsync("timelines", new HttpTimelineCreateRequest { Name = "t1" });
}
using var client = await CreateClientAsAdministrator();
@@ -91,7 +91,7 @@ namespace Timeline.Tests.IntegratedTests public async Task TimelineGet_IsHighlighField_Should_Work()
{
using var client = await CreateClientAsAdministrator();
- await client.TestPostAsync("timelines", new TimelineCreateRequest { Name = "t" });
+ await client.TestPostAsync("timelines", new HttpTimelineCreateRequest { Name = "t" });
{
var t = await client.TestGetAsync<HttpTimeline>("timelines/t");
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/HttpClientTestExtensions.cs b/BackEnd/Timeline.Tests/IntegratedTests/HttpClientTestExtensions.cs index b219f092..9848564f 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/HttpClientTestExtensions.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/HttpClientTestExtensions.cs @@ -78,6 +78,11 @@ namespace Timeline.Tests.IntegratedTests return await client.TestJsonSendAsync<T>(HttpMethod.Put, url, jsonBody, expectedStatusCode: expectedStatusCode);
}
+ public static async Task TestPatchAsync(this HttpClient client, string url, object? jsonBody = null, HttpStatusCode expectedStatusCode = HttpStatusCode.OK)
+ {
+ await client.TestJsonSendAsync(HttpMethod.Patch, url, jsonBody, expectedStatusCode: expectedStatusCode);
+ }
+
public static async Task<T> TestPatchAsync<T>(this HttpClient client, string url, object? jsonBody = null, HttpStatusCode expectedStatusCode = HttpStatusCode.OK)
{
return await client.TestJsonSendAsync<T>(HttpMethod.Patch, url, jsonBody, expectedStatusCode: expectedStatusCode);
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/SearchTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/SearchTest.cs new file mode 100644 index 00000000..f96acfea --- /dev/null +++ b/BackEnd/Timeline.Tests/IntegratedTests/SearchTest.cs @@ -0,0 +1,63 @@ +using FluentAssertions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Timeline.Models.Http;
+using Xunit;
+
+namespace Timeline.Tests.IntegratedTests
+{
+ public class SearchTest : IntegratedTestBase
+ {
+ [Fact]
+ public async Task TimelineSearch_Should_Work()
+ {
+ var client = await CreateClientAsUser();
+
+ {
+ await client.TestPostAsync("timelines", new HttpTimelineCreateRequest { Name = "hahaha" });
+ await client.TestPostAsync("timelines", new HttpTimelineCreateRequest { Name = "bababa" });
+ await client.TestPatchAsync("timelines/bababa", new HttpTimelinePatchRequest { Title = "hahaha" });
+ await client.TestPostAsync("timelines", new HttpTimelineCreateRequest { Name = "gagaga" });
+ }
+
+ {
+ var res = await client.TestGetAsync<List<HttpTimeline>>("search/timelines?q=hah");
+ res.Should().HaveCount(2);
+ res[0].Name.Should().Be("hahaha");
+ res[1].Name.Should().Be("bababa");
+ }
+
+ {
+ var res = await client.TestGetAsync<List<HttpTimeline>>("search/timelines?q=wuhu");
+ res.Should().BeEmpty();
+ }
+ }
+
+ [Fact]
+ public async Task UserSearch_Should_Work()
+ {
+ var client = await CreateClientAsAdministrator();
+
+ {
+ await client.TestPostAsync("userop/createuser", new HttpCreateUserRequest { Username = "hahaha", Password = "p" });
+ await client.TestPostAsync("userop/createuser", new HttpCreateUserRequest { Username = "bababa", Password = "p" });
+ await client.TestPatchAsync("users/bababa", new HttpUserPatchRequest { Nickname = "hahaha" });
+ await client.TestPostAsync("userop/createuser", new HttpCreateUserRequest { Username = "gagaga", Password = "p" });
+ }
+
+ {
+ var res = await client.TestGetAsync<List<HttpUser>>("search/users?q=hah");
+ res.Should().HaveCount(2);
+ res[0].Username.Should().Be("hahaha");
+ res[1].Username.Should().Be("bababa");
+ }
+
+ {
+ var res = await client.TestGetAsync<List<HttpUser>>("search/users?q=wuhu");
+ res.Should().BeEmpty();
+ }
+ }
+ }
+}
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs index 66261b36..4247e572 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs @@ -179,20 +179,20 @@ namespace Timeline.Tests.IntegratedTests {
{
using var client = await CreateDefaultClient();
- await client.TestPostAssertUnauthorizedAsync("timelines", new TimelineCreateRequest { Name = "aaa" });
+ await client.TestPostAssertUnauthorizedAsync("timelines", new HttpTimelineCreateRequest { Name = "aaa" });
}
{
using var client = await CreateClientAsUser();
- await client.TestPostAssertInvalidModelAsync("timelines", new TimelineCreateRequest { Name = "!!!" });
+ await client.TestPostAssertInvalidModelAsync("timelines", new HttpTimelineCreateRequest { Name = "!!!" });
{
- var body = await client.TestPostAsync<HttpTimeline>("timelines", new TimelineCreateRequest { Name = "aaa" });
+ var body = await client.TestPostAsync<HttpTimeline>("timelines", new HttpTimelineCreateRequest { Name = "aaa" });
body.Should().BeEquivalentTo(await client.GetTimelineAsync("aaa"));
}
- await client.TestPostAssertErrorAsync("timelines", new TimelineCreateRequest { Name = "aaa" }, errorCode: ErrorCodes.TimelineController.NameConflict);
+ await client.TestPostAssertErrorAsync("timelines", new HttpTimelineCreateRequest { Name = "aaa" }, errorCode: ErrorCodes.TimelineController.NameConflict);
}
}
diff --git a/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs b/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs deleted file mode 100644 index 90fb6463..00000000 --- a/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Threading.Tasks;
-using Timeline.Entities;
-using Timeline.Tests.Helpers;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Timeline.Tests.Services
-{
- public abstract class DatabaseBasedTest : IAsyncLifetime
- {
- protected TestDatabase TestDatabase { get; }
- protected DatabaseContext Database { get; private set; } = default!;
-
- private readonly ITestOutputHelper? _testOutputHelper;
-
- protected DatabaseBasedTest(bool databaseCreateUsers = true, ITestOutputHelper? testOutputHelper = null)
- {
- _testOutputHelper = testOutputHelper;
- TestDatabase = new TestDatabase(databaseCreateUsers);
- }
-
- protected DatabaseBasedTest(ITestOutputHelper? testOutputHelper) : this(true, testOutputHelper) { }
-
- public async Task InitializeAsync()
- {
- await TestDatabase.InitializeAsync();
- Database = TestDatabase.CreateContext(_testOutputHelper);
- await OnDatabaseCreatedAsync();
- OnDatabaseCreated();
- }
-
- public async Task DisposeAsync()
- {
- BeforeDatabaseDestroy();
- await BeforeDatabaseDestroyAsync();
- await Database.DisposeAsync();
- await TestDatabase.DisposeAsync();
- }
-
-
- protected virtual void OnDatabaseCreated() { }
- protected virtual void BeforeDatabaseDestroy() { }
-
-
- protected virtual Task OnDatabaseCreatedAsync()
- {
- return Task.CompletedTask;
- }
-
- protected virtual Task BeforeDatabaseDestroyAsync()
- {
- return Task.CompletedTask;
- }
- }
-}
diff --git a/BackEnd/Timeline.Tests/Services/SearchServiceTest.cs b/BackEnd/Timeline.Tests/Services/SearchServiceTest.cs new file mode 100644 index 00000000..968352c0 --- /dev/null +++ b/BackEnd/Timeline.Tests/Services/SearchServiceTest.cs @@ -0,0 +1,49 @@ +using FluentAssertions;
+using System.Threading.Tasks;
+using Timeline.Services;
+using Xunit;
+
+namespace Timeline.Tests.Services
+{
+ public class SearchServiceTest : ServiceTestBase
+ {
+ private SearchService _service = default!;
+
+ protected override void OnInitialize()
+ {
+ _service = new SearchService(Database);
+ }
+
+ [Fact]
+ public async Task TimelineSearch_Should_Work()
+ {
+ await TimelineService.CreateTimeline("hahaha", UserId);
+ var t2 = await TimelineService.CreateTimeline("bababa", UserId);
+ await TimelineService.ChangeProperty(t2.Id, new TimelineChangePropertyParams { Title = "hahaha" });
+ await TimelineService.CreateTimeline("bbbbbb", UserId);
+
+ var searchResult = await _service.SearchTimeline("hah");
+ searchResult.Items.Should().HaveCount(2);
+ searchResult.Items[0].Item.Name.Should().Be("hahaha");
+ searchResult.Items[0].Score.Should().Be(2);
+ searchResult.Items[1].Item.Name.Should().Be("bababa");
+ searchResult.Items[1].Score.Should().Be(1);
+ }
+
+ [Fact]
+ public async Task UserSearch_Should_Work()
+ {
+ await UserService.CreateUser("hahaha", "p");
+ var u2 = await UserService.CreateUser("bababa", "p");
+ await UserService.ModifyUser(u2.Id, new ModifyUserParams { Nickname = "hahaha" });
+ await UserService.CreateUser("bbbbbb", "p");
+
+ var searchResult = await _service.SearchUser("hah");
+ searchResult.Items.Should().HaveCount(2);
+ searchResult.Items[0].Item.Username.Should().Be("hahaha");
+ searchResult.Items[0].Score.Should().Be(2);
+ searchResult.Items[1].Item.Username.Should().Be("bababa");
+ searchResult.Items[1].Score.Should().Be(1);
+ }
+ }
+}
diff --git a/BackEnd/Timeline.Tests/Services/ServiceTestBase.cs b/BackEnd/Timeline.Tests/Services/ServiceTestBase.cs new file mode 100644 index 00000000..5a3e1e19 --- /dev/null +++ b/BackEnd/Timeline.Tests/Services/ServiceTestBase.cs @@ -0,0 +1,71 @@ +using Microsoft.Extensions.Logging.Abstractions;
+using System.Threading.Tasks;
+using Timeline.Entities;
+using Timeline.Services;
+using Timeline.Tests.Helpers;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Timeline.Tests.Services
+{
+ public abstract class ServiceTestBase : IAsyncLifetime
+ {
+ protected TestDatabase TestDatabase { get; }
+ protected DatabaseContext Database { get; private set; } = default!;
+
+ private readonly ITestOutputHelper? _testOutputHelper;
+
+ protected TestClock Clock { get; } = new TestClock();
+ protected UserService UserService { get; private set; } = default!;
+ protected TimelineService TimelineService { get; private set; } = default!;
+
+ protected long UserId { get; private set; }
+ protected long AdminId { get; private set; }
+
+ protected ServiceTestBase(bool databaseCreateUsers = true, ITestOutputHelper? testOutputHelper = null)
+ {
+ _testOutputHelper = testOutputHelper;
+ TestDatabase = new TestDatabase(databaseCreateUsers);
+ }
+
+ protected ServiceTestBase(ITestOutputHelper? testOutputHelper) : this(true, testOutputHelper) { }
+
+ public async Task InitializeAsync()
+ {
+ await TestDatabase.InitializeAsync();
+ Database = TestDatabase.CreateContext(_testOutputHelper);
+
+ UserService = new UserService(NullLogger<UserService>.Instance, Database, new PasswordService(), Clock);
+ TimelineService = new TimelineService(Database, UserService, Clock);
+
+ UserId = await UserService.GetUserIdByUsername("user");
+ AdminId = await UserService.GetUserIdByUsername("admin");
+
+ await OnInitializeAsync();
+ OnInitialize();
+ }
+
+ public async Task DisposeAsync()
+ {
+ OnDispose();
+ await OnDisposeAsync();
+ await Database.DisposeAsync();
+ await TestDatabase.DisposeAsync();
+ }
+
+
+ protected virtual void OnInitialize() { }
+ protected virtual void OnDispose() { }
+
+
+ protected virtual Task OnInitializeAsync()
+ {
+ return Task.CompletedTask;
+ }
+
+ protected virtual Task OnDisposeAsync()
+ {
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/BackEnd/Timeline.Tests/Services/UserDeleteServiceTest.cs b/BackEnd/Timeline.Tests/Services/UserDeleteServiceTest.cs index 59c0a9af..10014d2b 100644 --- a/BackEnd/Timeline.Tests/Services/UserDeleteServiceTest.cs +++ b/BackEnd/Timeline.Tests/Services/UserDeleteServiceTest.cs @@ -8,12 +8,12 @@ using Xunit; namespace Timeline.Tests.Services
{
- public class UserDeleteServiceTest : DatabaseBasedTest
+ public class UserDeleteServiceTest : ServiceTestBase
{
private readonly Mock<ITimelinePostService> _mockTimelinePostService = new Mock<ITimelinePostService>();
private UserDeleteService _service = default!;
- protected override void OnDatabaseCreated()
+ protected override void OnInitialize()
{
_service = new UserDeleteService(NullLogger<UserDeleteService>.Instance, Database, _mockTimelinePostService.Object);
}
diff --git a/BackEnd/Timeline.Tests/Services/UserPermissionServiceTest.cs b/BackEnd/Timeline.Tests/Services/UserPermissionServiceTest.cs index f20a7d62..0c43c025 100644 --- a/BackEnd/Timeline.Tests/Services/UserPermissionServiceTest.cs +++ b/BackEnd/Timeline.Tests/Services/UserPermissionServiceTest.cs @@ -7,7 +7,7 @@ using Xunit; namespace Timeline.Tests.Services
{
- public class UserPermissionServiceTest : DatabaseBasedTest
+ public class UserPermissionServiceTest : ServiceTestBase
{
private UserPermissionService _service = default!;
@@ -16,7 +16,7 @@ namespace Timeline.Tests.Services }
- protected override void OnDatabaseCreated()
+ protected override void OnInitialize()
{
_service = new UserPermissionService(Database);
}
diff --git a/BackEnd/Timeline/Controllers/SearchController.cs b/BackEnd/Timeline/Controllers/SearchController.cs new file mode 100644 index 00000000..dec876b6 --- /dev/null +++ b/BackEnd/Timeline/Controllers/SearchController.cs @@ -0,0 +1,61 @@ +using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Threading.Tasks;
+using Timeline.Models.Http;
+using Timeline.Models.Mapper;
+using Timeline.Services;
+
+namespace Timeline.Controllers
+{
+ /// <summary>
+ /// Api related to search timelines or users.
+ /// </summary>
+ [ApiController]
+ [ProducesErrorResponseType(typeof(CommonResponse))]
+ [Route("search")]
+ public class SearchController : Controller
+ {
+ private readonly ISearchService _service;
+ private readonly TimelineMapper _timelineMapper;
+ private readonly UserMapper _userMapper;
+
+ public SearchController(ISearchService service, TimelineMapper timelineMapper, UserMapper userMapper)
+ {
+ _service = service;
+ _timelineMapper = timelineMapper;
+ _userMapper = userMapper;
+ }
+
+ /// <summary>
+ /// Search timelines whose name or title contains query string case-insensitively.
+ /// </summary>
+ /// <param name="query">The string to contain.</param>
+ /// <returns>Timelines with most related at first.</returns>
+ [HttpGet("timelines")]
+ [ProducesResponseType(200)]
+ [ProducesResponseType(400)]
+ public async Task<ActionResult<List<HttpTimeline>>> TimelineSearch([FromQuery(Name = "q"), Required(AllowEmptyStrings = false)] string query)
+ {
+ var searchResult = await _service.SearchTimeline(query);
+ var timelines = searchResult.Items.Select(i => i.Item).ToList();
+ return await _timelineMapper.MapToHttp(timelines, Url, this.GetOptionalUserId());
+ }
+
+ /// <summary>
+ /// Search users whose username or nick contains query string case-insensitively.
+ /// </summary>
+ /// <param name="query">The string to contain.</param>
+ /// <returns>Users with most related at first.</returns>
+ [HttpGet("users")]
+ [ProducesResponseType(200)]
+ [ProducesResponseType(400)]
+ public async Task<ActionResult<List<HttpUser>>> UserSearch([FromQuery(Name = "q"), Required(AllowEmptyStrings = false)] string query)
+ {
+ var searchResult = await _service.SearchUser(query);
+ var users = searchResult.Items.Select(i => i.Item).ToList();
+ return await _userMapper.MapToHttp(users, Url);
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Controllers/TimelineController.cs b/BackEnd/Timeline/Controllers/TimelineController.cs index b2e37b15..5d484388 100644 --- a/BackEnd/Timeline/Controllers/TimelineController.cs +++ b/BackEnd/Timeline/Controllers/TimelineController.cs @@ -441,7 +441,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
- public async Task<ActionResult<HttpTimeline>> TimelineCreate([FromBody] TimelineCreateRequest body)
+ public async Task<ActionResult<HttpTimeline>> TimelineCreate([FromBody] HttpTimelineCreateRequest body)
{
var userId = this.GetUserId();
diff --git a/BackEnd/Timeline/Models/Http/TimelineController.cs b/BackEnd/Timeline/Models/Http/TimelineController.cs index f6039b35..257076f0 100644 --- a/BackEnd/Timeline/Models/Http/TimelineController.cs +++ b/BackEnd/Timeline/Models/Http/TimelineController.cs @@ -43,7 +43,7 @@ namespace Timeline.Models.Http /// <summary>
/// Create timeline request model.
/// </summary>
- public class TimelineCreateRequest
+ public class HttpTimelineCreateRequest
{
/// <summary>
/// Name of the new timeline. Must be a valid name.
diff --git a/BackEnd/Timeline/Services/SearchService.cs b/BackEnd/Timeline/Services/SearchService.cs new file mode 100644 index 00000000..680ef9e3 --- /dev/null +++ b/BackEnd/Timeline/Services/SearchService.cs @@ -0,0 +1,104 @@ +using Microsoft.EntityFrameworkCore;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Timeline.Entities;
+
+namespace Timeline.Services
+{
+ public class SearchResultItem<TItem>
+ {
+ public SearchResultItem(TItem item, int score)
+ {
+ Item = item;
+ Score = score;
+ }
+
+ public TItem Item { get; set; } = default!;
+
+ /// <summary>
+ /// Bigger is better.
+ /// </summary>
+ public int Score { get; set; }
+ }
+
+ public class SearchResult<TItem>
+ {
+#pragma warning disable CA2227 // Collection properties should be read only
+ public List<SearchResultItem<TItem>> Items { get; set; } = new();
+#pragma warning restore CA2227 // Collection properties should be read only
+ }
+
+ public interface ISearchService
+ {
+ /// <summary>
+ /// Search timelines whose name or title contains query string.
+ /// </summary>
+ /// <param name="query">String to contain.</param>
+ /// <returns>Search results.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="query"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="query"/> is empty.</exception>
+ /// <remarks>
+ /// Implementation should promise high score is at first.
+ /// </remarks>
+ Task<SearchResult<TimelineEntity>> SearchTimeline(string query);
+
+ /// <summary>
+ /// Search users whose username or nickname contains query string.
+ /// </summary>
+ /// <param name="query">String to contain.</param>
+ /// <returns>Search results.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="query"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="query"/> is empty.</exception>
+ /// <remarks>
+ /// Implementation should promise high score is at first.
+ /// </remarks>
+ Task<SearchResult<UserEntity>> SearchUser(string query);
+ }
+
+ public class SearchService : ISearchService
+ {
+ private readonly DatabaseContext _database;
+
+ public SearchService(DatabaseContext database)
+ {
+ _database = database;
+ }
+
+ public async Task<SearchResult<TimelineEntity>> SearchTimeline(string query)
+ {
+ if (query is null)
+ throw new ArgumentNullException(nameof(query));
+ if (query.Length == 0)
+ throw new ArgumentException("Query string can't be empty.", nameof(query));
+
+ var nameLikeTimelines = await _database.Timelines.Include(t => t.Owner).Where(t => t.Name == null ? t.Owner.Username.Contains(query) : t.Name.Contains(query)).ToListAsync();
+ var titleLikeTimelines = await _database.Timelines.Where(t => t.Title != null && t.Title.Contains(query)).ToListAsync();
+
+ var searchResult = new SearchResult<TimelineEntity>();
+ searchResult.Items.AddRange(nameLikeTimelines.Select(t => new SearchResultItem<TimelineEntity>(t, 2)));
+ searchResult.Items.AddRange(titleLikeTimelines.Select(t => new SearchResultItem<TimelineEntity>(t, 1)));
+
+ return searchResult;
+ }
+
+ public async Task<SearchResult<UserEntity>> SearchUser(string query)
+ {
+ if (query is null)
+ throw new ArgumentNullException(nameof(query));
+ if (query.Length == 0)
+ throw new ArgumentException("Query string can't be empty.", nameof(query));
+
+ var usernameLikeUsers = await _database.Users.Where(u => u.Username.Contains(query)).ToListAsync();
+ var nicknameLikeUsers = await _database.Users.Where(u => u.Nickname != null && u.Nickname.Contains(query)).ToListAsync();
+
+ var searchResult = new SearchResult<UserEntity>();
+ searchResult.Items.AddRange(usernameLikeUsers.Select(u => new SearchResultItem<UserEntity>(u, 2)));
+ searchResult.Items.AddRange(nicknameLikeUsers.Select(u => new SearchResultItem<UserEntity>(u, 1)));
+
+ return searchResult;
+
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Startup.cs b/BackEnd/Timeline/Startup.cs index cb99c138..0fab798b 100644 --- a/BackEnd/Timeline/Startup.cs +++ b/BackEnd/Timeline/Startup.cs @@ -120,6 +120,8 @@ namespace Timeline services.AddScoped<IHighlightTimelineService, HighlightTimelineService>();
services.AddScoped<IBookmarkTimelineService, BookmarkTimelineService>();
+ services.AddScoped<ISearchService, SearchService>();
+
services.AddOpenApiDocs();
if (_frontEndMode == FrontEndMode.Mock)
|