From 881f8559c1f5c515e40092957c5c2b1152a6ca48 Mon Sep 17 00:00:00 2001 From: sanchezvem Date: Tue, 21 Apr 2026 22:03:24 +0100 Subject: [PATCH] Added post endpoints and dto fix error in database --- BeReadyBackend.sln.DotSettings.user | 1 + BeReadyBackend/BeReadyDbContext.cs | 20 + BeReadyBackend/DTO/Posts/GetPostDto.cs | 3 +- .../Endpoints/Posts/GetAllPostsEndpoint.cs | 19 +- .../Endpoints/Posts/PatchLikeEndpoint.cs | 62 ++- .../MappingProfiles/EntityToDtoMappings.cs | 4 +- ...921_FixedErrorWithAuthorOfPost.Designer.cs | 491 ++++++++++++++++++ ...260421205921_FixedErrorWithAuthorOfPost.cs | 49 ++ .../BeReadyDbContextModelSnapshot.cs | 41 ++ BeReadyBackend/Models/Post.cs | 3 + BeReadyBackend/Models/UserPost.cs | 10 +- BeReadyBackend/Program.cs | 1 + .../Repositories/UserPostsRepository.cs | 5 + .../Specifications/Posts/GetPostByIdSpec.cs | 13 + .../Specifications/Posts/GetPostNotMeSpec.cs | 5 +- .../Posts/GetUserPostByIdsSpec.cs | 13 + .../Validators/Posts/GetPostDtoValidator.cs | 2 +- 17 files changed, 728 insertions(+), 14 deletions(-) create mode 100644 BeReadyBackend/Migrations/20260421205921_FixedErrorWithAuthorOfPost.Designer.cs create mode 100644 BeReadyBackend/Migrations/20260421205921_FixedErrorWithAuthorOfPost.cs create mode 100644 BeReadyBackend/Repositories/UserPostsRepository.cs create mode 100644 BeReadyBackend/Specifications/Posts/GetPostByIdSpec.cs create mode 100644 BeReadyBackend/Specifications/Posts/GetUserPostByIdsSpec.cs diff --git a/BeReadyBackend.sln.DotSettings.user b/BeReadyBackend.sln.DotSettings.user index 187bac8..1e1588b 100644 --- a/BeReadyBackend.sln.DotSettings.user +++ b/BeReadyBackend.sln.DotSettings.user @@ -1,4 +1,5 @@  + ForceIncluded ForceIncluded ForceIncluded ForceIncluded diff --git a/BeReadyBackend/BeReadyDbContext.cs b/BeReadyBackend/BeReadyDbContext.cs index 010933e..972612d 100644 --- a/BeReadyBackend/BeReadyDbContext.cs +++ b/BeReadyBackend/BeReadyDbContext.cs @@ -16,6 +16,7 @@ public class BeReadyDbContext : DbContext public DbSet UserGroups { get; set; } public DbSet UserRandomChallenges { get; set; } public DbSet Posts { get; set; } + public DbSet UserPosts { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { @@ -42,5 +43,24 @@ public class BeReadyDbContext : DbContext .WithMany() .HasForeignKey(x => x.FriendId) .OnDelete(DeleteBehavior.Restrict); + + + modelBuilder.Entity() + .HasOne(x => x.User) + .WithMany(x => x.Posts) + .HasForeignKey(x => x.UserId) + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasOne(x => x.User) + .WithMany(x => x.UserPosts) + .HasForeignKey(x => x.UserId) + .OnDelete(DeleteBehavior.NoAction); + + modelBuilder.Entity() + .HasOne(x => x.Post) + .WithMany(x => x.UserPosts) + .HasForeignKey(x => x.PostId) + .OnDelete(DeleteBehavior.Cascade); } } \ No newline at end of file diff --git a/BeReadyBackend/DTO/Posts/GetPostDto.cs b/BeReadyBackend/DTO/Posts/GetPostDto.cs index 1cdc150..7dd11da 100644 --- a/BeReadyBackend/DTO/Posts/GetPostDto.cs +++ b/BeReadyBackend/DTO/Posts/GetPostDto.cs @@ -6,7 +6,8 @@ public class GetPostDto public string? Libelle { get; set; } public DateTime CreationDate { get; set; } public int Likes { get; set; } + public bool IsLiked { get; set; } public int UserId { get; set; } - public string? UserUsername { get; set; } + public string? Username { get; set; } } \ No newline at end of file diff --git a/BeReadyBackend/Endpoints/Posts/GetAllPostsEndpoint.cs b/BeReadyBackend/Endpoints/Posts/GetAllPostsEndpoint.cs index e228b24..006c0f9 100644 --- a/BeReadyBackend/Endpoints/Posts/GetAllPostsEndpoint.cs +++ b/BeReadyBackend/Endpoints/Posts/GetAllPostsEndpoint.cs @@ -1,4 +1,5 @@ using BeReadyBackend.DTO.Posts; +using BeReadyBackend.Models; using BeReadyBackend.Repositories; using BeReadyBackend.Services; using BeReadyBackend.Specifications.Posts; @@ -12,10 +13,24 @@ public class GetAllPostsEndpoint(PostsRepository postsRepository, UserService us { Get("/Posts/"); } - + public override async Task HandleAsync(CancellationToken ct) { int userId = userService.GetUserIdFromToken(); - await Send.OkAsync(await postsRepository.ProjectToListAsync(new GetPostNotMeSpec(userId), ct), ct); + + List posts = await postsRepository.ListAsync(new GetPostNotMeSpec(userId), ct); + + List result = posts.Select(x => new GetPostDto + { + Id = x.Id, + Libelle = x.Libelle, + CreationDate = x.CreationDate, + Likes = x.Likes, + UserId = x.UserId, + Username = x.User?.Username, + IsLiked = x.UserPosts?.Count(y => y.UserId == userId) > 0 + }).ToList(); + + await Send.OkAsync(result, ct); } } \ No newline at end of file diff --git a/BeReadyBackend/Endpoints/Posts/PatchLikeEndpoint.cs b/BeReadyBackend/Endpoints/Posts/PatchLikeEndpoint.cs index 8e7255b..d6b8713 100644 --- a/BeReadyBackend/Endpoints/Posts/PatchLikeEndpoint.cs +++ b/BeReadyBackend/Endpoints/Posts/PatchLikeEndpoint.cs @@ -1,6 +1,64 @@ +using BeReadyBackend.Models; +using BeReadyBackend.Repositories; +using BeReadyBackend.Services; +using BeReadyBackend.Specifications.Posts; +using BeReadyBackend.Specifications.Users; +using FastEndpoints; + namespace BeReadyBackend.Endpoints.Posts; -public class PatchLikeEndpoint +public class PatchLikeRequest { - + public int PostId { get; set; } +} + +public class PatchLikeEndpoint(UserPostsRepository userPostsRepository, UsersRepository usersRepository, UserService userService, PostsRepository postsRepository) + : Endpoint +{ + public override void Configure() + { + Patch("/Posts/{@PostId}/Like", x => new { x.PostId }); + } + + public override async Task HandleAsync(PatchLikeRequest req, CancellationToken ct) + { + int userId = userService.GetUserIdFromToken(); + + Post? post = await postsRepository.SingleOrDefaultAsync(new GetPostByIdSpec(req.PostId), ct); + if (post is null) + { + await Send.NotFoundAsync(ct); + return; + } + + User? user = await usersRepository.SingleOrDefaultAsync(new GetUserByIdSpec(userId), ct); + if (user is null) + { + await Send.NotFoundAsync(ct); + return; + } + + UserPost? userPost = await userPostsRepository.SingleOrDefaultAsync(new GetUserPostByIdsSpec(userId, req.PostId), ct); + if (userPost is not null) + { + post.Likes = Math.Max(0, post.Likes - 1); + post.User!.TotalLikes = Math.Max(0, post.User.TotalLikes - 1); + + await userPostsRepository.DeleteAsync(userPost, ct); + await Send.NoContentAsync(ct); + return; + } + + post.Likes++; + post.User!.TotalLikes++; + + UserPost liked = new() + { + UserId = userId, + PostId = req.PostId + }; + + await userPostsRepository.AddAsync(liked, ct); + await Send.NoContentAsync(ct); + } } \ No newline at end of file diff --git a/BeReadyBackend/MappingProfiles/EntityToDtoMappings.cs b/BeReadyBackend/MappingProfiles/EntityToDtoMappings.cs index edbd50d..4682a96 100644 --- a/BeReadyBackend/MappingProfiles/EntityToDtoMappings.cs +++ b/BeReadyBackend/MappingProfiles/EntityToDtoMappings.cs @@ -39,7 +39,7 @@ public class EntityToDtoMappings : Profile .ForMember(dest => dest.ChallengeTitle, opt => opt.MapFrom(src => src.Label)) .ForMember(dest => dest.ChallengeDescription, opt => opt.MapFrom(src => src.Libelle)) .ForMember(dest => dest.ChallengeStartDate, opt => opt.MapFrom(src => DateOnly.FromDateTime(src.GeneratedAt!.Value))); - + CreateMap() .ForMember(dest => dest.Username, opt => opt.MapFrom(src => src.Friend!.Username)) .ForMember(dest => dest.Score, opt => opt.MapFrom(src => src.Friend!.TotalLikes)); @@ -66,7 +66,5 @@ public class EntityToDtoMappings : Profile CreateMap(); CreateMap(); - - CreateMap(); } } \ No newline at end of file diff --git a/BeReadyBackend/Migrations/20260421205921_FixedErrorWithAuthorOfPost.Designer.cs b/BeReadyBackend/Migrations/20260421205921_FixedErrorWithAuthorOfPost.Designer.cs new file mode 100644 index 0000000..4d65f3a --- /dev/null +++ b/BeReadyBackend/Migrations/20260421205921_FixedErrorWithAuthorOfPost.Designer.cs @@ -0,0 +1,491 @@ +// +using System; +using BeReadyBackend; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace BeReadyBackend.Migrations +{ + [DbContext(typeof(BeReadyDbContext))] + [Migration("20260421205921_FixedErrorWithAuthorOfPost")] + partial class FixedErrorWithAuthorOfPost + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.20") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("BeReadyBackend.Models.Achievement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Label") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Achievements"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.Designation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Label") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Designations"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("Label") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("GroupId") + .HasColumnType("int"); + + b.Property("Libelle") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SendDate") + .HasColumnType("datetime2"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.HasIndex("UserId"); + + b.ToTable("Messages"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.Post", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("Libelle") + .HasColumnType("nvarchar(max)"); + + b.Property("Likes") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Posts"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.RandomChallenge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("GeneratedAt") + .HasColumnType("datetime2"); + + b.Property("IsAlreadyPast") + .HasColumnType("bit"); + + b.Property("Label") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Libelle") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("RandomChallenges"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("DesignationId") + .HasColumnType("int"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b.Property("Salt") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Series") + .HasColumnType("int"); + + b.Property("TotalChallenge") + .HasColumnType("int"); + + b.Property("TotalLikes") + .HasColumnType("int"); + + b.Property("Username") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DesignationId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.UserAchievement", b => + { + b.Property("UserId") + .HasColumnType("int"); + + b.Property("AchievementId") + .HasColumnType("int"); + + b.HasKey("UserId", "AchievementId"); + + b.HasIndex("AchievementId"); + + b.ToTable("UserAchievements"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.UserFriend", b => + { + b.Property("UserId") + .HasColumnType("int"); + + b.Property("FriendId") + .HasColumnType("int"); + + b.Property("IsAccepted") + .HasColumnType("bit"); + + b.HasKey("UserId", "FriendId"); + + b.HasIndex("FriendId"); + + b.ToTable("UserFriends"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.UserGroup", b => + { + b.Property("UserId") + .HasColumnType("int"); + + b.Property("GroupId") + .HasColumnType("int"); + + b.Property("Grade") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("UserGroups"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.UserPost", b => + { + b.Property("UserId") + .HasColumnType("int"); + + b.Property("PostId") + .HasColumnType("int"); + + b.HasKey("UserId", "PostId"); + + b.HasIndex("PostId"); + + b.ToTable("UserPosts"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.UserRandomChallenge", b => + { + b.Property("UserId") + .HasColumnType("int"); + + b.Property("RandomChallengeId") + .HasColumnType("int"); + + b.Property("Proof") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "RandomChallengeId"); + + b.HasIndex("RandomChallengeId"); + + b.ToTable("UserRandomChallenges"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.Message", b => + { + b.HasOne("BeReadyBackend.Models.Group", "Group") + .WithMany("Messages") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BeReadyBackend.Models.User", "User") + .WithMany("Messages") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.Post", b => + { + b.HasOne("BeReadyBackend.Models.User", "User") + .WithMany("Posts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.User", b => + { + b.HasOne("BeReadyBackend.Models.Designation", "Designation") + .WithMany("Users") + .HasForeignKey("DesignationId"); + + b.Navigation("Designation"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.UserAchievement", b => + { + b.HasOne("BeReadyBackend.Models.Achievement", "Achievement") + .WithMany("UserAchievements") + .HasForeignKey("AchievementId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BeReadyBackend.Models.User", "User") + .WithMany("UserAchievements") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Achievement"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.UserFriend", b => + { + b.HasOne("BeReadyBackend.Models.User", "Friend") + .WithMany() + .HasForeignKey("FriendId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("BeReadyBackend.Models.User", "User") + .WithMany("UserFriends") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Friend"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.UserGroup", b => + { + b.HasOne("BeReadyBackend.Models.Group", "Group") + .WithMany("UserGroups") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BeReadyBackend.Models.User", "User") + .WithMany("UserGroups") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.UserPost", b => + { + b.HasOne("BeReadyBackend.Models.Post", "Post") + .WithMany("UserPosts") + .HasForeignKey("PostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BeReadyBackend.Models.User", "User") + .WithMany("UserPosts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Post"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.UserRandomChallenge", b => + { + b.HasOne("BeReadyBackend.Models.RandomChallenge", "RandomChallenge") + .WithMany("UserRandomChallenges") + .HasForeignKey("RandomChallengeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BeReadyBackend.Models.User", "User") + .WithMany("UserRandomChallenges") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RandomChallenge"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.Achievement", b => + { + b.Navigation("UserAchievements"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.Designation", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.Group", b => + { + b.Navigation("Messages"); + + b.Navigation("UserGroups"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.Post", b => + { + b.Navigation("UserPosts"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.RandomChallenge", b => + { + b.Navigation("UserRandomChallenges"); + }); + + modelBuilder.Entity("BeReadyBackend.Models.User", b => + { + b.Navigation("Messages"); + + b.Navigation("Posts"); + + b.Navigation("UserAchievements"); + + b.Navigation("UserFriends"); + + b.Navigation("UserGroups"); + + b.Navigation("UserPosts"); + + b.Navigation("UserRandomChallenges"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/BeReadyBackend/Migrations/20260421205921_FixedErrorWithAuthorOfPost.cs b/BeReadyBackend/Migrations/20260421205921_FixedErrorWithAuthorOfPost.cs new file mode 100644 index 0000000..cb8ab55 --- /dev/null +++ b/BeReadyBackend/Migrations/20260421205921_FixedErrorWithAuthorOfPost.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace BeReadyBackend.Migrations +{ + /// + public partial class FixedErrorWithAuthorOfPost : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "UserPosts", + columns: table => new + { + UserId = table.Column(type: "int", nullable: false), + PostId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserPosts", x => new { x.UserId, x.PostId }); + table.ForeignKey( + name: "FK_UserPosts_Posts_PostId", + column: x => x.PostId, + principalTable: "Posts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_UserPosts_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id"); + }); + + migrationBuilder.CreateIndex( + name: "IX_UserPosts_PostId", + table: "UserPosts", + column: "PostId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UserPosts"); + } + } +} diff --git a/BeReadyBackend/Migrations/BeReadyDbContextModelSnapshot.cs b/BeReadyBackend/Migrations/BeReadyDbContextModelSnapshot.cs index 03bbc7b..3762207 100644 --- a/BeReadyBackend/Migrations/BeReadyDbContextModelSnapshot.cs +++ b/BeReadyBackend/Migrations/BeReadyDbContextModelSnapshot.cs @@ -272,6 +272,21 @@ namespace BeReadyBackend.Migrations b.ToTable("UserGroups"); }); + modelBuilder.Entity("BeReadyBackend.Models.UserPost", b => + { + b.Property("UserId") + .HasColumnType("int"); + + b.Property("PostId") + .HasColumnType("int"); + + b.HasKey("UserId", "PostId"); + + b.HasIndex("PostId"); + + b.ToTable("UserPosts"); + }); + modelBuilder.Entity("BeReadyBackend.Models.UserRandomChallenge", b => { b.Property("UserId") @@ -386,6 +401,25 @@ namespace BeReadyBackend.Migrations b.Navigation("User"); }); + modelBuilder.Entity("BeReadyBackend.Models.UserPost", b => + { + b.HasOne("BeReadyBackend.Models.Post", "Post") + .WithMany("UserPosts") + .HasForeignKey("PostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BeReadyBackend.Models.User", "User") + .WithMany("UserPosts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Post"); + + b.Navigation("User"); + }); + modelBuilder.Entity("BeReadyBackend.Models.UserRandomChallenge", b => { b.HasOne("BeReadyBackend.Models.RandomChallenge", "RandomChallenge") @@ -422,6 +456,11 @@ namespace BeReadyBackend.Migrations b.Navigation("UserGroups"); }); + modelBuilder.Entity("BeReadyBackend.Models.Post", b => + { + b.Navigation("UserPosts"); + }); + modelBuilder.Entity("BeReadyBackend.Models.RandomChallenge", b => { b.Navigation("UserRandomChallenges"); @@ -439,6 +478,8 @@ namespace BeReadyBackend.Migrations b.Navigation("UserGroups"); + b.Navigation("UserPosts"); + b.Navigation("UserRandomChallenges"); }); #pragma warning restore 612, 618 diff --git a/BeReadyBackend/Models/Post.cs b/BeReadyBackend/Models/Post.cs index 1e82614..595b1a9 100644 --- a/BeReadyBackend/Models/Post.cs +++ b/BeReadyBackend/Models/Post.cs @@ -9,5 +9,8 @@ public class Post [Required] public DateTime CreationDate { get; set; } [Required] public int Likes { get; set; } = 0; + public User? User { get; set; } + [Required] public int UserId { get; set; } + public List? UserPosts { get; set; } } \ No newline at end of file diff --git a/BeReadyBackend/Models/UserPost.cs b/BeReadyBackend/Models/UserPost.cs index 4c4e83d..aa36fe1 100644 --- a/BeReadyBackend/Models/UserPost.cs +++ b/BeReadyBackend/Models/UserPost.cs @@ -1,12 +1,14 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.EntityFrameworkCore; + namespace BeReadyBackend.Models; +[PrimaryKey(nameof(UserId), nameof(PostId))] public class UserPost { public User? User { get; set; } - public int UserId { get; set; } + [Required] public int UserId { get; set; } public Post? Post { get; set; } - public int PostId { get; set; } - - public bool IsLiked { get; set; } + [Required] public int PostId { get; set; } } \ No newline at end of file diff --git a/BeReadyBackend/Program.cs b/BeReadyBackend/Program.cs index 4587ae0..d2cd4f3 100644 --- a/BeReadyBackend/Program.cs +++ b/BeReadyBackend/Program.cs @@ -45,6 +45,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddSignalR(); diff --git a/BeReadyBackend/Repositories/UserPostsRepository.cs b/BeReadyBackend/Repositories/UserPostsRepository.cs new file mode 100644 index 0000000..7effd87 --- /dev/null +++ b/BeReadyBackend/Repositories/UserPostsRepository.cs @@ -0,0 +1,5 @@ +using BeReadyBackend.Models; + +namespace BeReadyBackend.Repositories; + +public class UserPostsRepository(BeReadyDbContext beReadyDbContext, AutoMapper.IMapper mapper) : BeReadyRepository(beReadyDbContext, mapper); \ No newline at end of file diff --git a/BeReadyBackend/Specifications/Posts/GetPostByIdSpec.cs b/BeReadyBackend/Specifications/Posts/GetPostByIdSpec.cs new file mode 100644 index 0000000..e927281 --- /dev/null +++ b/BeReadyBackend/Specifications/Posts/GetPostByIdSpec.cs @@ -0,0 +1,13 @@ +using Ardalis.Specification; +using BeReadyBackend.Models; + +namespace BeReadyBackend.Specifications.Posts; + +public class GetPostByIdSpec : SingleResultSpecification +{ + public GetPostByIdSpec(int postId) + { + Query + .Where(x => x.Id == postId); + } +} \ No newline at end of file diff --git a/BeReadyBackend/Specifications/Posts/GetPostNotMeSpec.cs b/BeReadyBackend/Specifications/Posts/GetPostNotMeSpec.cs index 35aff4a..3aac12c 100644 --- a/BeReadyBackend/Specifications/Posts/GetPostNotMeSpec.cs +++ b/BeReadyBackend/Specifications/Posts/GetPostNotMeSpec.cs @@ -9,6 +9,9 @@ public class GetPostNotMeSpec : Specification { Query .Include(x => x.User) - .Where(x => x.UserId != userId); + .Include(x => x.UserPosts!) + .ThenInclude(up => up.User) + .Where(x => x.UserId != userId && + x.CreationDate >= DateTime.Today && x.CreationDate < DateTime.Today.AddDays(1)); } } \ No newline at end of file diff --git a/BeReadyBackend/Specifications/Posts/GetUserPostByIdsSpec.cs b/BeReadyBackend/Specifications/Posts/GetUserPostByIdsSpec.cs new file mode 100644 index 0000000..6b8a1a2 --- /dev/null +++ b/BeReadyBackend/Specifications/Posts/GetUserPostByIdsSpec.cs @@ -0,0 +1,13 @@ +using Ardalis.Specification; +using BeReadyBackend.Models; + +namespace BeReadyBackend.Specifications.Posts; + +public class GetUserPostByIdsSpec : SingleResultSpecification +{ + public GetUserPostByIdsSpec(int userId, int postId) + { + Query + .Where(x => x.UserId == userId && x.PostId == postId); + } +} \ No newline at end of file diff --git a/BeReadyBackend/Validators/Posts/GetPostDtoValidator.cs b/BeReadyBackend/Validators/Posts/GetPostDtoValidator.cs index 2139fdc..f17e556 100644 --- a/BeReadyBackend/Validators/Posts/GetPostDtoValidator.cs +++ b/BeReadyBackend/Validators/Posts/GetPostDtoValidator.cs @@ -36,7 +36,7 @@ public class GetPostDtoValidator : Validator .GreaterThan(0) .WithMessage("UserId must be greater than zero"); - RuleFor(x => x.UserUsername) + RuleFor(x => x.Username) .NotEmpty() .WithMessage("UserUsername cannot be empty"); }