From abd8d323bc8a954effb68cab76eca65e2254dbea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Pilgaard=20Gr=C3=B8ndahl?= Date: Fri, 30 Aug 2024 19:06:47 +0200 Subject: [PATCH 01/11] add dev settings and feature flags for posts --- src/web/Jordnaer/Pages/Shared/TopBar.razor | 20 +++++++++++------- src/web/Jordnaer/appsettings.Development.json | 21 +++++++++++++++++++ src/web/Jordnaer/appsettings.json | 4 ++-- 3 files changed, 36 insertions(+), 9 deletions(-) create mode 100644 src/web/Jordnaer/appsettings.Development.json diff --git a/src/web/Jordnaer/Pages/Shared/TopBar.razor b/src/web/Jordnaer/Pages/Shared/TopBar.razor index 0976f8e1..87a091a5 100644 --- a/src/web/Jordnaer/Pages/Shared/TopBar.razor +++ b/src/web/Jordnaer/Pages/Shared/TopBar.razor @@ -3,14 +3,20 @@ - + + + + + + + + + + + + + - - - - - - @* TODO: This doesn't look great as is. Rethink it and add it back? diff --git a/src/web/Jordnaer/appsettings.Development.json b/src/web/Jordnaer/appsettings.Development.json new file mode 100644 index 00000000..f61d97c5 --- /dev/null +++ b/src/web/Jordnaer/appsettings.Development.json @@ -0,0 +1,21 @@ +{ + "FeatureManagement": { + "Contacts": false, + "Events": false, + "Posts": true, + "AccountSettings": false, + "NotificationSettings": false + }, + "Serilog": { + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information", + "System.Net.Http": "Warning", + "Polly": "Warning", + "MudBlazor": "Information" + } + } + } +} \ No newline at end of file diff --git a/src/web/Jordnaer/appsettings.json b/src/web/Jordnaer/appsettings.json index 47e5ae97..ce80a23a 100644 --- a/src/web/Jordnaer/appsettings.json +++ b/src/web/Jordnaer/appsettings.json @@ -12,13 +12,13 @@ }, "Serilog": { "MinimumLevel": { - "Default": "Debug", + "Default": "Information", "Override": { "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information", "System.Net.Http": "Warning", "Polly": "Warning", - "MudBlazor": "Information" + "MudBlazor": "Warning" } } } From 8b0ca9d9d480a75da452c56fb4cb4b07c15c0250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Pilgaard=20Gr=C3=B8ndahl?= Date: Fri, 30 Aug 2024 20:07:16 +0200 Subject: [PATCH 02/11] make 90% of the backend for posts (not groupposts yet) --- .../Jordnaer.Shared/Database/GroupCategory.cs | 4 +- .../Jordnaer.Shared/Database/GroupPost.cs | 30 +++++ src/shared/Jordnaer.Shared/Database/Post.cs | 27 +++++ .../Jordnaer.Shared/Database/PostCategory.cs | 8 ++ .../Extensions/PostExtensions.cs | 24 ++++ src/shared/Jordnaer.Shared/Posts/PostDto.cs | 21 ++++ .../Jordnaer.Shared/Posts/PostSearchFilter.cs | 57 +++++++++ .../Jordnaer.Shared/Posts/PostSearchResult.cs | 7 ++ .../Jordnaer/Database/JordnaerDbContext.cs | 22 +++- .../Features/Metrics/JordnaerMetrics.cs | 5 + .../Features/PostSearch/PostSearchService.cs | 109 ++++++++++++++++++ .../Jordnaer/Features/Posts/PostService.cs | 75 ++++++++++++ .../Features/UserSearch/UserSearchService.cs | 11 +- 13 files changed, 392 insertions(+), 8 deletions(-) create mode 100644 src/shared/Jordnaer.Shared/Database/GroupPost.cs create mode 100644 src/shared/Jordnaer.Shared/Database/Post.cs create mode 100644 src/shared/Jordnaer.Shared/Database/PostCategory.cs create mode 100644 src/shared/Jordnaer.Shared/Extensions/PostExtensions.cs create mode 100644 src/shared/Jordnaer.Shared/Posts/PostDto.cs create mode 100644 src/shared/Jordnaer.Shared/Posts/PostSearchFilter.cs create mode 100644 src/shared/Jordnaer.Shared/Posts/PostSearchResult.cs create mode 100644 src/web/Jordnaer/Features/PostSearch/PostSearchService.cs create mode 100644 src/web/Jordnaer/Features/Posts/PostService.cs diff --git a/src/shared/Jordnaer.Shared/Database/GroupCategory.cs b/src/shared/Jordnaer.Shared/Database/GroupCategory.cs index cfbda385..191c0de4 100644 --- a/src/shared/Jordnaer.Shared/Database/GroupCategory.cs +++ b/src/shared/Jordnaer.Shared/Database/GroupCategory.cs @@ -2,7 +2,7 @@ namespace Jordnaer.Shared; public class GroupCategory { - public required Guid GroupId { get; set; } + public required Guid GroupId { get; set; } - public required int CategoryId { get; set; } + public required int CategoryId { get; set; } } \ No newline at end of file diff --git a/src/shared/Jordnaer.Shared/Database/GroupPost.cs b/src/shared/Jordnaer.Shared/Database/GroupPost.cs new file mode 100644 index 00000000..16807bdc --- /dev/null +++ b/src/shared/Jordnaer.Shared/Database/GroupPost.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; + +namespace Jordnaer.Shared; + +public class GroupPost +{ + + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public required Guid Id { get; init; } + + [StringLength(1000, ErrorMessage = "Opslag må højest være 1000 karakterer lang.")] + [Required(AllowEmptyStrings = false, ErrorMessage = "Opslag skal have mindst 1 karakter.")] + public required string Text { get; init; } + + public DateTimeOffset CreatedUtc { get; init; } = DateTimeOffset.UtcNow; + + public int? ZipCode { get; set; } + + [ForeignKey(nameof(UserProfile))] + public required string UserProfileId { get; init; } = null!; + + public UserProfile UserProfile { get; init; } = null!; + + [ForeignKey(nameof(Group))] + public required Guid GroupId { get; init; } + + public Group Group { get; init; } = null!; +} \ No newline at end of file diff --git a/src/shared/Jordnaer.Shared/Database/Post.cs b/src/shared/Jordnaer.Shared/Database/Post.cs new file mode 100644 index 00000000..0df55993 --- /dev/null +++ b/src/shared/Jordnaer.Shared/Database/Post.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Jordnaer.Shared; + +public class Post +{ + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public required Guid Id { get; init; } + + [StringLength(1000, ErrorMessage = "Opslag må højest være 1000 karakterer lang.")] + [Required(AllowEmptyStrings = false, ErrorMessage = "Opslag skal have mindst 1 karakter.")] + public required string Text { get; init; } + + public DateTimeOffset CreatedUtc { get; init; } = DateTimeOffset.UtcNow; + + public int? ZipCode { get; set; } + public string? City { get; set; } + + [ForeignKey(nameof(UserProfile))] + public required string UserProfileId { get; init; } = null!; + + public UserProfile UserProfile { get; init; } = null!; + + public List Categories { get; set; } = []; +} \ No newline at end of file diff --git a/src/shared/Jordnaer.Shared/Database/PostCategory.cs b/src/shared/Jordnaer.Shared/Database/PostCategory.cs new file mode 100644 index 00000000..3a3b2bc4 --- /dev/null +++ b/src/shared/Jordnaer.Shared/Database/PostCategory.cs @@ -0,0 +1,8 @@ +namespace Jordnaer.Shared; + +public class PostCategory +{ + public required Guid PostId { get; set; } + + public required int CategoryId { get; set; } +} \ No newline at end of file diff --git a/src/shared/Jordnaer.Shared/Extensions/PostExtensions.cs b/src/shared/Jordnaer.Shared/Extensions/PostExtensions.cs new file mode 100644 index 00000000..e2c20788 --- /dev/null +++ b/src/shared/Jordnaer.Shared/Extensions/PostExtensions.cs @@ -0,0 +1,24 @@ +namespace Jordnaer.Shared; + +public static class PostExtensions +{ + public static PostDto ToPostDto(this Post post) + { + return new PostDto + { + Id = post.Id, + Text = post.Text, + CreatedUtc = post.CreatedUtc, + Author = new UserSlim + { + Id = post.UserProfileId, + ProfilePictureUrl = post.UserProfile.ProfilePictureUrl, + UserName = post.UserProfile.UserName, + DisplayName = post.UserProfile.DisplayName + }, + City = post.City, + ZipCode = post.ZipCode, + Categories = post.Categories.Select(category => category.Name).ToList() + }; + } +} \ No newline at end of file diff --git a/src/shared/Jordnaer.Shared/Posts/PostDto.cs b/src/shared/Jordnaer.Shared/Posts/PostDto.cs new file mode 100644 index 00000000..d52672a9 --- /dev/null +++ b/src/shared/Jordnaer.Shared/Posts/PostDto.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; + +namespace Jordnaer.Shared; + +public class PostDto +{ + public required Guid Id { get; init; } + + [StringLength(1000, ErrorMessage = "Opslag må højest være 1000 karakterer lang.")] + [Required(AllowEmptyStrings = false, ErrorMessage = "Opslag skal have mindst 1 karakter.")] + public required string Text { get; init; } + + public int? ZipCode { get; set; } + public string? City { get; set; } + + public DateTimeOffset CreatedUtc { get; init; } + + public required UserSlim Author { get; init; } + + public List Categories { get; set; } = []; +} \ No newline at end of file diff --git a/src/shared/Jordnaer.Shared/Posts/PostSearchFilter.cs b/src/shared/Jordnaer.Shared/Posts/PostSearchFilter.cs new file mode 100644 index 00000000..736f85ed --- /dev/null +++ b/src/shared/Jordnaer.Shared/Posts/PostSearchFilter.cs @@ -0,0 +1,57 @@ +using System.ComponentModel.DataAnnotations; + +namespace Jordnaer.Shared; + +public class PostSearchFilter +{ + public string? Contents { get; set; } + public string[]? Categories { get; set; } = []; + + /// + /// Only show user results within this many kilometers of the . + /// + [Range(1, 50, ErrorMessage = "Afstand skal være mellem 1 og 50 km")] + [LocationRequired] + public int? WithinRadiusKilometers { get; set; } + + [RadiusRequired] + public string? Location { get; set; } + + public int PageNumber { get; set; } = 1; + public int PageSize { get; set; } = 10; +} + +file class RadiusRequiredAttribute : ValidationAttribute +{ + protected override ValidationResult IsValid(object? value, ValidationContext validationContext) + { + var postSearchFilter = (PostSearchFilter)validationContext.ObjectInstance; + + if (postSearchFilter.WithinRadiusKilometers is null && string.IsNullOrEmpty(postSearchFilter.Location)) + { + return ValidationResult.Success!; + } + + return postSearchFilter.WithinRadiusKilometers is null + ? new ValidationResult("Radius skal vælges når et område er valgt.") + : ValidationResult.Success!; + } +} + +file class LocationRequiredAttribute : ValidationAttribute +{ + protected override ValidationResult IsValid(object? value, ValidationContext validationContext) + { + var postSearchFilter = (PostSearchFilter)validationContext.ObjectInstance; + + if (postSearchFilter.WithinRadiusKilometers is null && string.IsNullOrEmpty(postSearchFilter.Location)) + { + return ValidationResult.Success!; + + } + + return string.IsNullOrEmpty(postSearchFilter.Location) + ? new ValidationResult("Område skal vælges når en radius er valgt.") + : ValidationResult.Success!; + } +} diff --git a/src/shared/Jordnaer.Shared/Posts/PostSearchResult.cs b/src/shared/Jordnaer.Shared/Posts/PostSearchResult.cs new file mode 100644 index 00000000..36021609 --- /dev/null +++ b/src/shared/Jordnaer.Shared/Posts/PostSearchResult.cs @@ -0,0 +1,7 @@ +namespace Jordnaer.Shared; + +public class PostSearchResult +{ + public List Posts { get; set; } = []; + public int TotalCount { get; set; } +} diff --git a/src/web/Jordnaer/Database/JordnaerDbContext.cs b/src/web/Jordnaer/Database/JordnaerDbContext.cs index 446c7f24..c8da2acc 100644 --- a/src/web/Jordnaer/Database/JordnaerDbContext.cs +++ b/src/web/Jordnaer/Database/JordnaerDbContext.cs @@ -11,7 +11,7 @@ public class JordnaerDbContext : IdentityDbContext public DbSet Categories { get; set; } = default!; public DbSet UserProfileCategories { get; set; } = default!; public DbSet UserContacts { get; set; } = default!; - public DbSet Chats { get; set; } = default!; + public DbSet Chats { get; set; } = default!; public DbSet ChatMessages { get; set; } = default!; public DbSet UnreadMessages { get; set; } = default; public DbSet UserChats { get; set; } = default!; @@ -20,8 +20,28 @@ public class JordnaerDbContext : IdentityDbContext public DbSet GroupMemberships { get; set; } = default!; public DbSet GroupCategories { get; set; } = default!; + public DbSet Posts { get; set; } = default!; + public DbSet GroupPosts { get; set; } = default!; + protected override void OnModelCreating(ModelBuilder modelBuilder) { + modelBuilder.Entity() + .HasOne(e => e.UserProfile) + .WithMany(); + + modelBuilder.Entity() + .HasMany(e => e.Categories) + .WithMany() + .UsingEntity(); + + modelBuilder.Entity() + .HasOne(e => e.UserProfile) + .WithMany(); + + modelBuilder.Entity() + .HasOne(e => e.Group) + .WithMany(); + modelBuilder.Entity() .HasMany(e => e.Members) .WithMany(e => e.Groups) diff --git a/src/web/Jordnaer/Features/Metrics/JordnaerMetrics.cs b/src/web/Jordnaer/Features/Metrics/JordnaerMetrics.cs index eb48da13..d74702e7 100644 --- a/src/web/Jordnaer/Features/Metrics/JordnaerMetrics.cs +++ b/src/web/Jordnaer/Features/Metrics/JordnaerMetrics.cs @@ -40,6 +40,11 @@ internal static class JordnaerMetrics internal static readonly Counter UserSearchesCounter = Meter.CreateCounter("jordnaer_user_user_searches_total"); + internal static readonly Counter PostSearchesCounter = + Meter.CreateCounter("jordnaer_post_post_searches_total"); + internal static readonly Counter PostsCreatedCounter = + Meter.CreateCounter("jordnaer_post_posts_created_total"); + internal static readonly Counter SponsorAdViewCounter = Meter.CreateCounter("jordnaer_ad_sponsor_views_total"); } \ No newline at end of file diff --git a/src/web/Jordnaer/Features/PostSearch/PostSearchService.cs b/src/web/Jordnaer/Features/PostSearch/PostSearchService.cs new file mode 100644 index 00000000..14bf9489 --- /dev/null +++ b/src/web/Jordnaer/Features/PostSearch/PostSearchService.cs @@ -0,0 +1,109 @@ +using Jordnaer.Database; +using Jordnaer.Features.Metrics; +using Jordnaer.Features.Search; +using Jordnaer.Shared; +using Microsoft.EntityFrameworkCore; + +namespace Jordnaer.Features.PostSearch; + +public interface IPostSearchService +{ + Task GetPostsAsync(PostSearchFilter filter, + CancellationToken cancellationToken = default); +} + +public class PostSearchService( + IDbContextFactory contextFactory, + IZipCodeService zipCodeService) : IPostSearchService +{ + public async Task GetPostsAsync(PostSearchFilter filter, + CancellationToken cancellationToken = default) + { + JordnaerMetrics.PostSearchesCounter.Add(1, MakeTagList(filter)); + + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var query = context.Posts + .AsNoTracking() + .AsQueryable(); + + query = ApplyCategoryFilter(filter, query); + query = await ApplyLocationFilterAsync(filter, query, cancellationToken); + query = ApplyContentFilter(filter.Contents, query); + + var postsToSkip = filter.PageNumber == 1 + ? 0 + : (filter.PageNumber - 1) * filter.PageSize; + + var posts = await query.OrderByDescending(x => x.CreatedUtc) + .Skip(postsToSkip) + .Take(filter.PageSize) + .Select(x => x.ToPostDto()) + .ToListAsync(cancellationToken); + + var totalCount = await query.CountAsync(cancellationToken); + + return new PostSearchResult + { + Posts = posts, + TotalCount = totalCount + }; + } + + internal async Task> ApplyLocationFilterAsync( + PostSearchFilter filter, + IQueryable posts, + CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(filter.Location) || filter.WithinRadiusKilometers is null) + { + return posts; + } + + var (zipCodesWithinCircle, searchedZipCode) = await zipCodeService.GetZipCodesNearLocationAsync( + filter.Location, + filter.WithinRadiusKilometers.Value, + cancellationToken); + + if (zipCodesWithinCircle.Count is 0 || searchedZipCode is null) + { + return posts; + } + + posts = posts.Where(user => user.ZipCode != null && + zipCodesWithinCircle.Contains(user.ZipCode.Value)); + + return posts; + } + + internal static IQueryable ApplyCategoryFilter(PostSearchFilter filter, IQueryable posts) + { + if (filter.Categories is not null && filter.Categories.Length > 0) + { + posts = posts.Where( + user => user.Categories.Any(category => filter.Categories.Contains(category.Name))); + } + + return posts; + } + + internal static IQueryable ApplyContentFilter(string? filter, IQueryable posts) + { + if (!string.IsNullOrWhiteSpace(filter)) + { + posts = posts.Where(post => EF.Functions.Like(post.Text, $"%{filter}%")); + } + + return posts; + } + + private static ReadOnlySpan> MakeTagList(PostSearchFilter filter) + { + return new KeyValuePair[] + { + new(nameof(filter.Location), filter.Location), + new(nameof(filter.Categories), string.Join(',', filter.Categories ?? [])), + new(nameof(filter.WithinRadiusKilometers), filter.WithinRadiusKilometers) + }; + } +} \ No newline at end of file diff --git a/src/web/Jordnaer/Features/Posts/PostService.cs b/src/web/Jordnaer/Features/Posts/PostService.cs new file mode 100644 index 00000000..08cd0290 --- /dev/null +++ b/src/web/Jordnaer/Features/Posts/PostService.cs @@ -0,0 +1,75 @@ +using Jordnaer.Database; +using Jordnaer.Shared; +using Microsoft.EntityFrameworkCore; +using OneOf; +using OneOf.Types; + +namespace Jordnaer.Features.Posts; + +public interface IPostService +{ + Task> GetPostAsync(Guid postId, + CancellationToken cancellationToken = default); + + Task>> CreatePostAsync(Post post, + CancellationToken cancellationToken = default); + + Task>> DeletePostAsync(Guid postId, + CancellationToken cancellationToken = default); +} +public class PostService(IDbContextFactory contextFactory) : IPostService +{ + public async Task> GetPostAsync(Guid postId, + CancellationToken cancellationToken = default) + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var post = await context.Posts + .Where(x => x.Id == postId) + .Select(x => x.ToPostDto()) + .FirstOrDefaultAsync(cancellationToken); + + return post is null + ? new NotFound() + : post; + } + + public async Task>> CreatePostAsync(Post post, + CancellationToken cancellationToken = default) + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + if (await context.Posts + .AsNoTracking() + .AnyAsync(x => x.Id == post.Id && + x.UserProfileId == post.UserProfileId, + cancellationToken)) + { + return new Error("Opslaget eksisterer allerede."); + } + + context.Posts.Add(post); + + await context.SaveChangesAsync(cancellationToken); + + return new Success(); + } + + public async Task>> DeletePostAsync(Guid postId, + CancellationToken cancellationToken = default) + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var post = await context.Posts.FindAsync([postId], cancellationToken); + if (post is null) + { + return new Error("Opslaget blev ikke fundet."); + } + + await context.Posts + .Where(x => x.Id == postId) + .ExecuteDeleteAsync(cancellationToken); + + return new Success(); + } +} diff --git a/src/web/Jordnaer/Features/UserSearch/UserSearchService.cs b/src/web/Jordnaer/Features/UserSearch/UserSearchService.cs index 0b5b8f97..51bea4b1 100644 --- a/src/web/Jordnaer/Features/UserSearch/UserSearchService.cs +++ b/src/web/Jordnaer/Features/UserSearch/UserSearchService.cs @@ -25,7 +25,7 @@ public async Task> GetUsersByNameAsync(string currentUserId, stri var firstTenUsers = await users .Where(user => user.Id != currentUserId) .OrderBy(user => searchString.StartsWith(searchString)) - .Take(11) + .Take(11) // We take 11 to see if there are more than 10 users (to show a "more" button) .Select(user => new UserSlim { ProfilePictureUrl = user.ProfilePictureUrl, @@ -91,7 +91,7 @@ public async Task GetUsersAsync(UserSearchFilter filter, Cance return new UserSearchResult { TotalCount = totalCount, Users = paginatedUsers }; } - private async Task<(IQueryable UserProfiles, bool AppliedOrdering)> ApplyLocationFilterAsync( + internal async Task<(IQueryable UserProfiles, bool AppliedOrdering)> ApplyLocationFilterAsync( UserSearchFilter filter, IQueryable users, CancellationToken cancellationToken = default) @@ -111,6 +111,7 @@ public async Task GetUsersAsync(UserSearchFilter filter, Cance return (users, false); } + // TODO: This ordering should be done after the "client" has received it, to enable caching users = users.Where(user => user.ZipCode != null && zipCodesWithinCircle.Contains(user.ZipCode.Value)) .OrderBy(user => Math.Abs(user.ZipCode!.Value - searchedZipCode.Value)); @@ -118,7 +119,7 @@ public async Task GetUsersAsync(UserSearchFilter filter, Cance return (users, true); } - private static IQueryable ApplyCategoryFilter(UserSearchFilter filter, IQueryable users) + internal static IQueryable ApplyCategoryFilter(UserSearchFilter filter, IQueryable users) { if (filter.Categories is not null && filter.Categories.Length > 0) { @@ -129,7 +130,7 @@ private static IQueryable ApplyCategoryFilter(UserSearchFilter filt return users; } - private static IQueryable ApplyNameFilter(string? filter, IQueryable users) + internal static IQueryable ApplyNameFilter(string? filter, IQueryable users) { if (!string.IsNullOrWhiteSpace(filter)) { @@ -140,7 +141,7 @@ private static IQueryable ApplyNameFilter(string? filter, IQueryabl return users; } - private static IQueryable ApplyChildFilters(UserSearchFilter filter, IQueryable users) + internal static IQueryable ApplyChildFilters(UserSearchFilter filter, IQueryable users) { if (filter.ChildGender is not null) { From e12efccb2a39e1df13300fd0dbe6afd061d0cf35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Pilgaard=20Gr=C3=B8ndahl?= Date: Fri, 30 Aug 2024 21:58:22 +0200 Subject: [PATCH 03/11] add error handling --- .../Features/PostSearch/PostSearchService.cs | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/web/Jordnaer/Features/PostSearch/PostSearchService.cs b/src/web/Jordnaer/Features/PostSearch/PostSearchService.cs index 14bf9489..027518a0 100644 --- a/src/web/Jordnaer/Features/PostSearch/PostSearchService.cs +++ b/src/web/Jordnaer/Features/PostSearch/PostSearchService.cs @@ -1,22 +1,26 @@ using Jordnaer.Database; using Jordnaer.Features.Metrics; using Jordnaer.Features.Search; +using Jordnaer.Models; using Jordnaer.Shared; using Microsoft.EntityFrameworkCore; +using OneOf; +using OneOf.Types; namespace Jordnaer.Features.PostSearch; public interface IPostSearchService { - Task GetPostsAsync(PostSearchFilter filter, + Task>> GetPostsAsync(PostSearchFilter filter, CancellationToken cancellationToken = default); } public class PostSearchService( IDbContextFactory contextFactory, - IZipCodeService zipCodeService) : IPostSearchService + IZipCodeService zipCodeService, + ILogger logger) : IPostSearchService { - public async Task GetPostsAsync(PostSearchFilter filter, + public async Task>> GetPostsAsync(PostSearchFilter filter, CancellationToken cancellationToken = default) { JordnaerMetrics.PostSearchesCounter.Add(1, MakeTagList(filter)); @@ -35,19 +39,29 @@ public async Task GetPostsAsync(PostSearchFilter filter, ? 0 : (filter.PageNumber - 1) * filter.PageSize; - var posts = await query.OrderByDescending(x => x.CreatedUtc) - .Skip(postsToSkip) - .Take(filter.PageSize) - .Select(x => x.ToPostDto()) - .ToListAsync(cancellationToken); - - var totalCount = await query.CountAsync(cancellationToken); - - return new PostSearchResult + try { - Posts = posts, - TotalCount = totalCount - }; + var posts = await query.OrderByDescending(x => x.CreatedUtc) + .Skip(postsToSkip) + .Take(filter.PageSize) + .Select(x => x.ToPostDto()) + .ToListAsync(cancellationToken); + + var totalCount = await query.CountAsync(cancellationToken); + + return new PostSearchResult + { + Posts = posts, + TotalCount = totalCount + }; + } + catch (Exception exception) + { + logger.LogError(exception, "Exception occurred during post search. " + + "Query: {Query}", query.ToQueryString()); + } + + return new Error(ErrorMessages.Something_Went_Wrong_Try_Again); } internal async Task> ApplyLocationFilterAsync( From 7797602383b2746f67dd9195b8ba2922e0b5a3ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Pilgaard=20Gr=C3=B8ndahl?= Date: Fri, 30 Aug 2024 22:01:31 +0200 Subject: [PATCH 04/11] add todos --- src/web/Jordnaer/Features/GroupSearch/GroupSearchService.cs | 1 + src/web/Jordnaer/Features/UserSearch/UserSearchService.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/web/Jordnaer/Features/GroupSearch/GroupSearchService.cs b/src/web/Jordnaer/Features/GroupSearch/GroupSearchService.cs index 379d62ac..f43234b3 100644 --- a/src/web/Jordnaer/Features/GroupSearch/GroupSearchService.cs +++ b/src/web/Jordnaer/Features/GroupSearch/GroupSearchService.cs @@ -34,6 +34,7 @@ public async Task GetGroupsAsync(GroupSearchFilter filter, groups = groups.OrderBy(user => user.CreatedUtc); } + // TODO: Try-catch and error in return type var groupsToSkip = filter.PageNumber == 1 ? 0 : (filter.PageNumber - 1) * filter.PageSize; var paginatedGroups = await groups .Skip(groupsToSkip) diff --git a/src/web/Jordnaer/Features/UserSearch/UserSearchService.cs b/src/web/Jordnaer/Features/UserSearch/UserSearchService.cs index 51bea4b1..50ecc808 100644 --- a/src/web/Jordnaer/Features/UserSearch/UserSearchService.cs +++ b/src/web/Jordnaer/Features/UserSearch/UserSearchService.cs @@ -58,6 +58,7 @@ public async Task GetUsersAsync(UserSearchFilter filter, Cance } // TODO: This uses a ton of memory, Dapper? (60+mb) + // TODO: Try-catch and error in return type var usersToSkip = filter.PageNumber == 1 ? 0 : (filter.PageNumber - 1) * filter.PageSize; var paginatedUsers = await users .Skip(usersToSkip) From 1bf2dc89bdf180f7eab7a354d7d7ed64900e1bbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Pilgaard=20Gr=C3=B8ndahl?= Date: Fri, 30 Aug 2024 22:32:22 +0200 Subject: [PATCH 05/11] initial draft of the post page, mostly copy paste --- .../Features/GroupPosts/PostService.cs | 76 +++++++++++++++++++ .../Features/Posts/CreatePostComponent.razor | 41 ++++++++++ .../Features/Posts/PostSearchForm.razor | 70 +++++++++++++++++ .../Posts/PostSearchResultComponent.razor | 36 +++++++++ src/web/Jordnaer/Pages/Posts/Posts.razor | 75 ++++++++++++++++++ src/web/Jordnaer/_Imports.razor | 3 + 6 files changed, 301 insertions(+) create mode 100644 src/web/Jordnaer/Features/GroupPosts/PostService.cs create mode 100644 src/web/Jordnaer/Features/Posts/CreatePostComponent.razor create mode 100644 src/web/Jordnaer/Features/Posts/PostSearchForm.razor create mode 100644 src/web/Jordnaer/Features/Posts/PostSearchResultComponent.razor create mode 100644 src/web/Jordnaer/Pages/Posts/Posts.razor diff --git a/src/web/Jordnaer/Features/GroupPosts/PostService.cs b/src/web/Jordnaer/Features/GroupPosts/PostService.cs new file mode 100644 index 00000000..91b41302 --- /dev/null +++ b/src/web/Jordnaer/Features/GroupPosts/PostService.cs @@ -0,0 +1,76 @@ +using Jordnaer.Database; +using Jordnaer.Shared; +using Microsoft.EntityFrameworkCore; +using OneOf; +using OneOf.Types; + +namespace Jordnaer.Features.GroupPosts; + +public interface IGroupPostService +{ + Task> GetPostAsync(Guid postId, + CancellationToken cancellationToken = default); + + Task>> CreatePostAsync(GroupPost post, + CancellationToken cancellationToken = default); + + Task>> DeletePostAsync(Guid postId, + CancellationToken cancellationToken = default); +} +// TODO: This is just a copy of PostService, make it Group specific +public class GroupPostService(IDbContextFactory contextFactory) : IGroupPostService +{ + public async Task> GetPostAsync(Guid postId, + CancellationToken cancellationToken = default) + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var post = await context.Posts + .Where(x => x.Id == postId) + .Select(x => x.ToPostDto()) + .FirstOrDefaultAsync(cancellationToken); + + return post is null + ? new NotFound() + : post; + } + + public async Task>> CreatePostAsync(GroupPost post, + CancellationToken cancellationToken = default) + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + if (await context.Posts + .AsNoTracking() + .AnyAsync(x => x.Id == post.Id && + x.UserProfileId == post.UserProfileId, + cancellationToken)) + { + return new Error("Opslaget eksisterer allerede."); + } + + context.GroupPosts.Add(post); + + await context.SaveChangesAsync(cancellationToken); + + return new Success(); + } + + public async Task>> DeletePostAsync(Guid postId, + CancellationToken cancellationToken = default) + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var post = await context.Posts.FindAsync([postId], cancellationToken); + if (post is null) + { + return new Error("Opslaget blev ikke fundet."); + } + + await context.Posts + .Where(x => x.Id == postId) + .ExecuteDeleteAsync(cancellationToken); + + return new Success(); + } +} diff --git a/src/web/Jordnaer/Features/Posts/CreatePostComponent.razor b/src/web/Jordnaer/Features/Posts/CreatePostComponent.razor new file mode 100644 index 00000000..bf84829c --- /dev/null +++ b/src/web/Jordnaer/Features/Posts/CreatePostComponent.razor @@ -0,0 +1,41 @@ +@inject CurrentUser CurrentUser +@inject IPostService PostService +@inject IPostSearchService PostSearchService +@inject NavigationManager Navigation +@inject IJSRuntime JsRuntime +@inject ISnackbar Snackbar + + + Del et opslag + + Send + +@code { + private string? newPostContent; + + private async Task CreatePost() + { + if (!string.IsNullOrWhiteSpace(newPostContent)) + { + var post = new Post + { + Id = Guid.NewGuid(), + Text = newPostContent, + CreatedUtc = DateTime.UtcNow, + UserProfileId = CurrentUser.Id, + // Add other necessary properties here + }; + + var result = await PostService.CreatePostAsync(post); + + result.Switch( + success => + { + Snackbar.Add("Opslaget blev oprettet.", Severity.Success); + newPostContent = string.Empty; + }, + error => Snackbar.Add(error.Value, Severity.Error) + ); + } + } +} diff --git a/src/web/Jordnaer/Features/Posts/PostSearchForm.razor b/src/web/Jordnaer/Features/Posts/PostSearchForm.razor new file mode 100644 index 00000000..09dcd47b --- /dev/null +++ b/src/web/Jordnaer/Features/Posts/PostSearchForm.razor @@ -0,0 +1,70 @@ +@inject NavigationManager Navigation +@inject IJSRuntime JsRuntime + + + + +

+ Find Opslag +

+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +@code +{ + [Parameter, EditorRequired] + public required PostSearchFilter Filter { get; set; } + + [Parameter, EditorRequired] + public EventCallback FilterChanged { get; set; } + + [Parameter, EditorRequired] + public required EventCallback OnValidSubmit { get; set; } + + private static readonly PostSearchFilter DefaultFilter = new(); + + private bool _recentlyClearedForm = false; + + private async Task ClearFilter() + { + Filter = new PostSearchFilter(); + await FilterChanged.InvokeAsync(Filter); + + var uriWithQuery = new Uri(Navigation.Uri); + var uriWithoutQuery = uriWithQuery.GetLeftPart(UriPartial.Path); + + _recentlyClearedForm = true; + + await JsRuntime.NavigateTo(uriWithoutQuery); + } +} diff --git a/src/web/Jordnaer/Features/Posts/PostSearchResultComponent.razor b/src/web/Jordnaer/Features/Posts/PostSearchResultComponent.razor new file mode 100644 index 00000000..b8f278f4 --- /dev/null +++ b/src/web/Jordnaer/Features/Posts/PostSearchResultComponent.razor @@ -0,0 +1,36 @@ +@inject IJSRuntime JsRuntime +@inject NavigationManager Navigation + + + + @foreach (var post in SearchResult.Posts) + { + + + + @post.Text + @post.CreatedUtc.ToLocalTime().ToString("g") + + + + } + + + +@code { + [Parameter, EditorRequired] + public required PostSearchFilter Filter { get; set; } + + [Parameter, EditorRequired] + public required PostSearchResult SearchResult { get; set; } + + [Parameter, EditorRequired] + public EventCallback SelectedPageChanged { get; set; } +} diff --git a/src/web/Jordnaer/Pages/Posts/Posts.razor b/src/web/Jordnaer/Pages/Posts/Posts.razor new file mode 100644 index 00000000..da8a7aa1 --- /dev/null +++ b/src/web/Jordnaer/Pages/Posts/Posts.razor @@ -0,0 +1,75 @@ +@page "/posts" + +@inject IPostSearchService PostSearchService +@inject ISnackbar Snackbar + +@attribute [Sitemap] + + + + + + + + + + + + @if (!_hasSearched) + { + return; + } + + @if (_searchResult.TotalCount == 0) + { + + + Ingen opslag fundet. + + + return; + } + + + + + + + +@code { + private PostSearchFilter _filter = new(); + private PostSearchResult _searchResult = new(); + + private bool _isSearching = false; + private bool _hasSearched = false; + + private async Task Search() + { + _isSearching = true; + + var result = await PostSearchService.GetPostsAsync(_filter); + + result.Switch( + success => + { + _searchResult = success; + Snackbar.Add($"{_searchResult.TotalCount} opslag fundet.", Severity.Success); + }, + error => Snackbar.Add(error.Value, Severity.Error) + ); + + _hasSearched = true; + _isSearching = false; + } + + private async Task OnSelectedPageChanged(int selectedPage) + { + _filter.PageNumber = selectedPage; + await Search(); + } +} diff --git a/src/web/Jordnaer/_Imports.razor b/src/web/Jordnaer/_Imports.razor index 5f6612d1..7e51cddb 100644 --- a/src/web/Jordnaer/_Imports.razor +++ b/src/web/Jordnaer/_Imports.razor @@ -20,6 +20,9 @@ @using Jordnaer.Features.Images @using Jordnaer.Features.Membership @using Jordnaer.Features.Metrics +@using Jordnaer.Features.Posts +@using Jordnaer.Features.PostSearch +@using Jordnaer.Features.GroupPosts @using Jordnaer.Features.Profile @using Jordnaer.Features.Search @using Jordnaer.Features.SEO From bcff743aeb36520d7944f879bf7ee3106a70411f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Pilgaard=20Gr=C3=B8ndahl?= Date: Sun, 1 Sep 2024 21:39:02 +0200 Subject: [PATCH 06/11] split into components --- .../Features/Posts/PostCardComponent.razor | 22 +++++++++++++++++++ .../Posts/PostSearchResultComponent.razor | 9 ++------ 2 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 src/web/Jordnaer/Features/Posts/PostCardComponent.razor diff --git a/src/web/Jordnaer/Features/Posts/PostCardComponent.razor b/src/web/Jordnaer/Features/Posts/PostCardComponent.razor new file mode 100644 index 00000000..0a7c1d9f --- /dev/null +++ b/src/web/Jordnaer/Features/Posts/PostCardComponent.razor @@ -0,0 +1,22 @@ + + + @Post.Text + @Post.CreatedUtc.ToLocalTime().ToString("g") + @if (Post.ZipCode is not null && !string.IsNullOrEmpty(Post.City)) + { + @Post.ZipCode, @Post.City + } + + @Post.Author.DisplayName + + + + +@code { + + [Parameter, EditorRequired] + public required PostDto Post { get; set; } + +} \ No newline at end of file diff --git a/src/web/Jordnaer/Features/Posts/PostSearchResultComponent.razor b/src/web/Jordnaer/Features/Posts/PostSearchResultComponent.razor index b8f278f4..65506010 100644 --- a/src/web/Jordnaer/Features/Posts/PostSearchResultComponent.razor +++ b/src/web/Jordnaer/Features/Posts/PostSearchResultComponent.razor @@ -1,4 +1,4 @@ -@inject IJSRuntime JsRuntime +@inject IJSRuntime JsRuntime @inject NavigationManager Navigation @@ -6,12 +6,7 @@ @foreach (var post in SearchResult.Posts) { - - - @post.Text - @post.CreatedUtc.ToLocalTime().ToString("g") - - + } From f8851b95b0f27ad4f9878e63c7fbef758610b06e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Pilgaard=20Gr=C3=B8ndahl?= Date: Sun, 1 Sep 2024 21:39:10 +0200 Subject: [PATCH 07/11] more post work --- src/shared/Jordnaer.Shared/Database/Post.cs | 6 +- .../Features/Posts/CreatePostComponent.razor | 130 +++++++++++++----- .../Features/Posts/PostSearchForm.razor | 72 ++++++---- 3 files changed, 141 insertions(+), 67 deletions(-) diff --git a/src/shared/Jordnaer.Shared/Database/Post.cs b/src/shared/Jordnaer.Shared/Database/Post.cs index 0df55993..babe59e1 100644 --- a/src/shared/Jordnaer.Shared/Database/Post.cs +++ b/src/shared/Jordnaer.Shared/Database/Post.cs @@ -11,15 +11,15 @@ public class Post [StringLength(1000, ErrorMessage = "Opslag må højest være 1000 karakterer lang.")] [Required(AllowEmptyStrings = false, ErrorMessage = "Opslag skal have mindst 1 karakter.")] - public required string Text { get; init; } + public required string Text { get; set; } - public DateTimeOffset CreatedUtc { get; init; } = DateTimeOffset.UtcNow; + public DateTimeOffset CreatedUtc { get; set; } = DateTimeOffset.UtcNow; public int? ZipCode { get; set; } public string? City { get; set; } [ForeignKey(nameof(UserProfile))] - public required string UserProfileId { get; init; } = null!; + public required string UserProfileId { get; set; } = null!; public UserProfile UserProfile { get; init; } = null!; diff --git a/src/web/Jordnaer/Features/Posts/CreatePostComponent.razor b/src/web/Jordnaer/Features/Posts/CreatePostComponent.razor index bf84829c..63fb52e7 100644 --- a/src/web/Jordnaer/Features/Posts/CreatePostComponent.razor +++ b/src/web/Jordnaer/Features/Posts/CreatePostComponent.razor @@ -1,41 +1,99 @@ -@inject CurrentUser CurrentUser +@inject IProfileCache ProfileCache +@inject ICategoryCache CategoryCache @inject IPostService PostService -@inject IPostSearchService PostSearchService -@inject NavigationManager Navigation -@inject IJSRuntime JsRuntime @inject ISnackbar Snackbar - - Del et opslag - - Send - +@attribute [StreamRendering] + + + + + + + Du skal være logget ind for at lave et opslag. + Log ind + + + + Del et opslag + + + + + Slå op + + + + + + + @code { - private string? newPostContent; - - private async Task CreatePost() - { - if (!string.IsNullOrWhiteSpace(newPostContent)) - { - var post = new Post - { - Id = Guid.NewGuid(), - Text = newPostContent, - CreatedUtc = DateTime.UtcNow, - UserProfileId = CurrentUser.Id, - // Add other necessary properties here - }; - - var result = await PostService.CreatePostAsync(post); - - result.Switch( - success => - { - Snackbar.Add("Opslaget blev oprettet.", Severity.Success); - newPostContent = string.Empty; - }, - error => Snackbar.Add(error.Value, Severity.Error) - ); - } - } + public bool _includeAreaInPost { get; set; } = true; + + private string? _postText; + + private readonly Post _post = new Post + { + Id = NewId.NextGuid(), + Text = string.Empty, + UserProfileId = string.Empty + }; + + private IEnumerable _categories = []; + private bool _isLoading = true; + private UserProfile? _userProfile; + + protected override async Task OnInitializedAsync() + { + _categories = await CategoryCache.GetOrCreateCategoriesAsync(); + _isLoading = false; + + _userProfile = await ProfileCache.GetProfileAsync(); + } + + private async Task CreatePost() + { + if (string.IsNullOrEmpty(_postText)) + { + return; + } + + if (_userProfile is null) + { + Snackbar.Add("Du skal være logget ind for at lave et opslag.", Severity.Info); + return; + } + + _post.Text = _postText; + _post.CreatedUtc = DateTimeOffset.UtcNow; + _post.UserProfileId = _userProfile.Id; + + if (_includeAreaInPost) + { + _post.ZipCode = _userProfile.ZipCode; + _post.City = _userProfile.City; + } + + var result = await PostService.CreatePostAsync(_post); + + result.Switch( + _ => + { + Snackbar.Add("Opslaget blev oprettet.", Severity.Success); + _postText = string.Empty; + }, + error => Snackbar.Add(error.Value, Severity.Error) + ); + } + + private void SelectedCategoriesChanged(IEnumerable categories) + => _post.Categories = _categories.Where(e => categories.Contains(e.Name)).ToList(); } diff --git a/src/web/Jordnaer/Features/Posts/PostSearchForm.razor b/src/web/Jordnaer/Features/Posts/PostSearchForm.razor index 09dcd47b..d1a62e84 100644 --- a/src/web/Jordnaer/Features/Posts/PostSearchForm.razor +++ b/src/web/Jordnaer/Features/Posts/PostSearchForm.razor @@ -1,7 +1,4 @@ -@inject NavigationManager Navigation -@inject IJSRuntime JsRuntime - - +

@@ -14,26 +11,46 @@ - - + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + + @@ -48,11 +65,9 @@ [Parameter, EditorRequired] public EventCallback FilterChanged { get; set; } - [Parameter, EditorRequired] + [Parameter] public required EventCallback OnValidSubmit { get; set; } - private static readonly PostSearchFilter DefaultFilter = new(); - private bool _recentlyClearedForm = false; private async Task ClearFilter() @@ -60,11 +75,12 @@ Filter = new PostSearchFilter(); await FilterChanged.InvokeAsync(Filter); - var uriWithQuery = new Uri(Navigation.Uri); - var uriWithoutQuery = uriWithQuery.GetLeftPart(UriPartial.Path); - _recentlyClearedForm = true; - - await JsRuntime.NavigateTo(uriWithoutQuery); + } + + private void LocationChanged(string location) + { + Filter.Location = location; + Filter.WithinRadiusKilometers ??= 10; } } From db5b46af3f36c2668beb28cc04ee031c594530f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Pilgaard=20Gr=C3=B8ndahl?= Date: Sun, 1 Sep 2024 21:44:57 +0200 Subject: [PATCH 08/11] Update CreatePostComponent.razor --- src/web/Jordnaer/Features/Posts/CreatePostComponent.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web/Jordnaer/Features/Posts/CreatePostComponent.razor b/src/web/Jordnaer/Features/Posts/CreatePostComponent.razor index 63fb52e7..41d19064 100644 --- a/src/web/Jordnaer/Features/Posts/CreatePostComponent.razor +++ b/src/web/Jordnaer/Features/Posts/CreatePostComponent.razor @@ -28,7 +28,7 @@ SelectedValuesChanged="SelectedCategoriesChanged"> Slå op - + From 17aa5e7e21c965e748d6c949ddcab60437037d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Pilgaard=20Gr=C3=B8ndahl?= Date: Tue, 3 Sep 2024 20:33:22 +0200 Subject: [PATCH 09/11] fixes --- src/web/Jordnaer/Features/Posts/CreatePostComponent.razor | 2 +- src/web/Jordnaer/Features/Posts/PostCardComponent.razor | 7 ++++--- src/web/Jordnaer/Features/Posts/PostSearchForm.razor | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/web/Jordnaer/Features/Posts/CreatePostComponent.razor b/src/web/Jordnaer/Features/Posts/CreatePostComponent.razor index 41d19064..413b6eeb 100644 --- a/src/web/Jordnaer/Features/Posts/CreatePostComponent.razor +++ b/src/web/Jordnaer/Features/Posts/CreatePostComponent.razor @@ -28,7 +28,7 @@ SelectedValuesChanged="SelectedCategoriesChanged"> Slå op - + diff --git a/src/web/Jordnaer/Features/Posts/PostCardComponent.razor b/src/web/Jordnaer/Features/Posts/PostCardComponent.razor index 0a7c1d9f..a962bf7f 100644 --- a/src/web/Jordnaer/Features/Posts/PostCardComponent.razor +++ b/src/web/Jordnaer/Features/Posts/PostCardComponent.razor @@ -6,15 +6,16 @@ { @Post.ZipCode, @Post.City } - + @Post.Author.DisplayName @code { + private string _title => $"Gå til {Post.Author.UserName}'s profil"; [Parameter, EditorRequired] public required PostDto Post { get; set; } diff --git a/src/web/Jordnaer/Features/Posts/PostSearchForm.razor b/src/web/Jordnaer/Features/Posts/PostSearchForm.razor index d1a62e84..c7c24956 100644 --- a/src/web/Jordnaer/Features/Posts/PostSearchForm.razor +++ b/src/web/Jordnaer/Features/Posts/PostSearchForm.razor @@ -62,7 +62,7 @@ [Parameter, EditorRequired] public required PostSearchFilter Filter { get; set; } - [Parameter, EditorRequired] + [Parameter] public EventCallback FilterChanged { get; set; } [Parameter] From 8a4030290ac69face349058a58c40fc4e87f1460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Pilgaard=20Gr=C3=B8ndahl?= Date: Sat, 14 Sep 2024 20:50:31 +0200 Subject: [PATCH 10/11] register post services --- .../Features/PostSearch/WebApplicationExtensions.cs | 11 +++++++++++ .../Features/Posts/WebApplicationExtensions.cs | 11 +++++++++++ src/web/Jordnaer/Program.cs | 4 ++++ 3 files changed, 26 insertions(+) create mode 100644 src/web/Jordnaer/Features/PostSearch/WebApplicationExtensions.cs create mode 100644 src/web/Jordnaer/Features/Posts/WebApplicationExtensions.cs diff --git a/src/web/Jordnaer/Features/PostSearch/WebApplicationExtensions.cs b/src/web/Jordnaer/Features/PostSearch/WebApplicationExtensions.cs new file mode 100644 index 00000000..3c754d0e --- /dev/null +++ b/src/web/Jordnaer/Features/PostSearch/WebApplicationExtensions.cs @@ -0,0 +1,11 @@ +namespace Jordnaer.Features.PostSearch; + +public static class WebApplicationExtensions +{ + public static WebApplicationBuilder AddPostSearchService(this WebApplicationBuilder builder) + { + builder.Services.AddScoped(); + + return builder; + } +} \ No newline at end of file diff --git a/src/web/Jordnaer/Features/Posts/WebApplicationExtensions.cs b/src/web/Jordnaer/Features/Posts/WebApplicationExtensions.cs new file mode 100644 index 00000000..abacb9dc --- /dev/null +++ b/src/web/Jordnaer/Features/Posts/WebApplicationExtensions.cs @@ -0,0 +1,11 @@ +namespace Jordnaer.Features.Posts; + +public static class WebApplicationExtensions +{ + public static WebApplicationBuilder AddPostService(this WebApplicationBuilder builder) + { + builder.Services.AddScoped(); + + return builder; + } +} \ No newline at end of file diff --git a/src/web/Jordnaer/Program.cs b/src/web/Jordnaer/Program.cs index 3364b73c..83e4a32e 100644 --- a/src/web/Jordnaer/Program.cs +++ b/src/web/Jordnaer/Program.cs @@ -21,6 +21,8 @@ using Microsoft.Extensions.Diagnostics.HealthChecks; using Jordnaer.Database; using Jordnaer.Features.Membership; +using Jordnaer.Features.Posts; +using Jordnaer.Features.PostSearch; using Jordnaer.Features.Search; using Microsoft.Net.Http.Headers; using Sidio.Sitemap.AspNetCore; @@ -79,6 +81,8 @@ builder.AddProfileServices(); builder.AddChatServices(); builder.AddMembershipServices(); +builder.AddPostService(); +builder.AddPostSearchService(); builder.AddMudBlazor(); From a8cf72360aa764a4cc4f1cfa115398b3184f8a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Pilgaard=20Gr=C3=B8ndahl?= Date: Sun, 15 Dec 2024 19:56:57 +0100 Subject: [PATCH 11/11] remove lighthouse CI --- .github/workflows/lighthouse.yml | 51 -------------------------------- Jordnaer.sln | 1 - 2 files changed, 52 deletions(-) delete mode 100644 .github/workflows/lighthouse.yml diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml deleted file mode 100644 index 2069ff0f..00000000 --- a/.github/workflows/lighthouse.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Run Lighthouse - -on: - workflow_run: - workflows: ["Deploy Website"] - types: - - completed - workflow_dispatch: - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: "pages" - cancel-in-progress: false - -env: - WEBSITE_URL: https://mini-moeder.dk -jobs: - lighthose: - # Only run this if the workflow that triggered it was successful - if: ${{ github.event.workflow_run.conclusion == 'success' }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Install Dependencies - run: npm install -g @unlighthouse/cli puppeteer - - - name: Run Unlighthouse CI - run: unlighthouse-ci --site "${{ env.WEBSITE_URL }}" --build-static - - - name: Setup Pages - uses: actions/configure-pages@v5 - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: ".unlighthouse" - - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/Jordnaer.sln b/Jordnaer.sln index a445f634..fb48474d 100644 --- a/Jordnaer.sln +++ b/Jordnaer.sln @@ -13,7 +13,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .github\workflows\container_apps_chat_ci.yml = .github\workflows\container_apps_chat_ci.yml Directory.Build.props = Directory.Build.props .github\workflows\github-release.yml = .github\workflows\github-release.yml - .github\workflows\lighthouse.yml = .github\workflows\lighthouse.yml README.md = README.md .github\workflows\website_backend_ci.yml = .github\workflows\website_backend_ci.yml .github\workflows\website_cd.yml = .github\workflows\website_cd.yml