From 5b1f99bae1ffded6993f4497da02c0658c8273cb Mon Sep 17 00:00:00 2001 From: Julien LEICHER Date: Wed, 15 Dec 2021 16:05:50 +0100 Subject: [PATCH] ajout auteur des liens / commentaires --- .../Controllers/AccountsController.cs | 45 ++- .../Controllers/LinksController.cs | 3 + Apps/HackerNet.Api/Program.cs | 1 + Apps/HackerNet.Api/api.http | 11 + .../appsettings.Development.json | 2 +- .../Views/Shared/_LinkCard.cshtml | 1 + HackerNet.Application/LinkService.cs | 18 +- HackerNet.Domain/Comment.cs | 4 +- HackerNet.Domain/Link.cs | 4 +- HackerNet.Infrastructure/AspNet/Extensions.cs | 5 +- .../AspNet/HttpCurrentUser.cs | 23 ++ .../20211215145254_AddCreatedBy.Designer.cs | 349 ++++++++++++++++++ .../Migrations/20211215145254_AddCreatedBy.cs | 81 ++++ .../Migrations/HackerContextModelSnapshot.cs | 27 ++ .../EntityFramework/EFReadStore.cs | 36 +- .../CommentEntityTypeConfiguration.cs | 6 + .../LinkEntityTypeConfiguration.cs | 7 + 17 files changed, 601 insertions(+), 22 deletions(-) create mode 100644 HackerNet.Infrastructure/AspNet/HttpCurrentUser.cs create mode 100644 HackerNet.Infrastructure/Migrations/20211215145254_AddCreatedBy.Designer.cs create mode 100644 HackerNet.Infrastructure/Migrations/20211215145254_AddCreatedBy.cs diff --git a/Apps/HackerNet.Api/Controllers/AccountsController.cs b/Apps/HackerNet.Api/Controllers/AccountsController.cs index 8bac125..6bfa697 100644 --- a/Apps/HackerNet.Api/Controllers/AccountsController.cs +++ b/Apps/HackerNet.Api/Controllers/AccountsController.cs @@ -1,6 +1,9 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; using HackerNet.Api.Models; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using Microsoft.IdentityModel.Tokens; namespace HackerNet.Api.Controllers; @@ -10,17 +13,21 @@ public class AccountsController : ControllerBase { private readonly UserManager _userManager; private readonly SignInManager _signInManager; + private readonly IUserClaimsPrincipalFactory _claimsFactory; + private readonly TokenValidation _tokenValidation; - public AccountsController(UserManager userManager, SignInManager signInManager) + public AccountsController(UserManager userManager, SignInManager signInManager, IUserClaimsPrincipalFactory claimsFactory, TokenValidation tokenValidation) { _userManager = userManager; _signInManager = signInManager; + _claimsFactory = claimsFactory; + _tokenValidation = tokenValidation; } [HttpGet("me")] - public ActionResult Me() + public ActionResult Me() { - throw new NotImplementedException(); + return User.Identity.Name; } [HttpPost] @@ -36,4 +43,36 @@ public class AccountsController : ControllerBase return CreatedAtAction(nameof(Me), null); } + + [HttpPost("token")] + public async Task> Signin(SignupLoginViewModel cmd) + { + var user = await _userManager.FindByNameAsync(cmd.Username); + + if (user == null) + { + return BadRequest(); + } + + var result = await _signInManager.CheckPasswordSignInAsync(user, cmd.Password, false); + + if (!result.Succeeded) + { + return BadRequest(); + } + + var principal = await _claimsFactory.CreateAsync(user); + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = (ClaimsIdentity)principal.Identity, + Expires = DateTime.UtcNow.AddDays(7), + Issuer = _tokenValidation.Issuer, + Audience = _tokenValidation.Audience, + SigningCredentials = new SigningCredentials( + new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(_tokenValidation.Key)) + , SecurityAlgorithms.HmacSha256Signature) + }; + + return new JwtSecurityTokenHandler().CreateEncodedJwt(tokenDescriptor); + } } \ No newline at end of file diff --git a/Apps/HackerNet.Api/Controllers/LinksController.cs b/Apps/HackerNet.Api/Controllers/LinksController.cs index 7ced968..88c8986 100644 --- a/Apps/HackerNet.Api/Controllers/LinksController.cs +++ b/Apps/HackerNet.Api/Controllers/LinksController.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using HackerNet.Application; using HackerNet.Infrastructure.AspNet.Filters; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace HackerNet.Api.Controllers; @@ -52,6 +53,7 @@ public class LinksController : ControllerBase } [HttpPost("{id:guid}/comments")] + [Authorize] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public ActionResult PublishComment(Guid id, PublishCommentBody cmd) @@ -76,6 +78,7 @@ public class LinksController : ControllerBase /// /// [HttpPost] + [Authorize] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status201Created)] public ActionResult PublishLink(PublishLinkCommand cmd) diff --git a/Apps/HackerNet.Api/Program.cs b/Apps/HackerNet.Api/Program.cs index 5437b7b..ec329d8 100644 --- a/Apps/HackerNet.Api/Program.cs +++ b/Apps/HackerNet.Api/Program.cs @@ -12,6 +12,7 @@ var tokenValidation = builder.Configuration .GetSection("TokenValidation") .Get(); +builder.Services.AddSingleton(tokenValidation); builder.Services.AddHackerNetServicesEntityFramework(builder.Configuration); builder.Services.AddControllers(); builder.Services diff --git a/Apps/HackerNet.Api/api.http b/Apps/HackerNet.Api/api.http index f961ce5..57fdc07 100644 --- a/Apps/HackerNet.Api/api.http +++ b/Apps/HackerNet.Api/api.http @@ -6,6 +6,7 @@ GET {{url}}/api/links POST {{url}}/api/links Content-Type: application/json +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiJiMGFiMDU0Zi1lMjI0LTRlNGMtYmY0MC0xY2UxNzU0ZWNiMTciLCJuYW1lIjoidGVzdCIsIkFzcE5ldC5JZGVudGl0eS5TZWN1cml0eVN0YW1wIjoiRlNHVk01U0ZFRUpSTzVORFIzR0ZPN1ZRWkRJQ1c2QVEiLCJuYmYiOjE2Mzk1Nzg3NjEsImV4cCI6MTY0MDE4MzU2MSwiaWF0IjoxNjM5NTc4NzYxLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo3MjUyLyIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjcyNTIvIn0.wjL5ALncw5JtNe5YpZD-uQAz2-BvbznOqqN1xgZpjAU { "url": "https://localhost:7252/api/links", @@ -30,6 +31,16 @@ Content-Type: application/json POST {{url}}/api/accounts Content-Type: application/json +{ + "username": "test", + "password": "G6:c`bzr2h#Pq;4" +} + +### + +POST {{url}}/api/accounts/token +Content-Type: application/json + { "username": "test", "password": "G6:c`bzr2h#Pq;4" diff --git a/Apps/HackerNet.Api/appsettings.Development.json b/Apps/HackerNet.Api/appsettings.Development.json index c926728..029dd0e 100644 --- a/Apps/HackerNet.Api/appsettings.Development.json +++ b/Apps/HackerNet.Api/appsettings.Development.json @@ -1,7 +1,7 @@ { "Logging": { "LogLevel": { - "Default": "Information", + "Default": "Debug", "Microsoft.AspNetCore": "Warning" } }, diff --git a/Apps/HackerNet.Web/Views/Shared/_LinkCard.cshtml b/Apps/HackerNet.Web/Views/Shared/_LinkCard.cshtml index b222fa7..94283d7 100644 --- a/Apps/HackerNet.Web/Views/Shared/_LinkCard.cshtml +++ b/Apps/HackerNet.Web/Views/Shared/_LinkCard.cshtml @@ -9,5 +9,6 @@ Détail du lien Ajouter un commentaire

@Model.CommentsCount commentaire@(Model.CommentsCount > 1 ? "s" : "")

+

@Model.Author

\ No newline at end of file diff --git a/HackerNet.Application/LinkService.cs b/HackerNet.Application/LinkService.cs index 6a53db0..13904da 100644 --- a/HackerNet.Application/LinkService.cs +++ b/HackerNet.Application/LinkService.cs @@ -7,20 +7,25 @@ public class LinkService private readonly ILinkRepository _repository; private readonly ICommentRepository _commentRepository; private readonly IReadStore _readStore; + private readonly ICurrentUser _currentUser; public LinkService( ILinkRepository repository, ICommentRepository commentRepository, - IReadStore readStore) + IReadStore readStore, + ICurrentUser currentUser) { _repository = repository; _commentRepository = commentRepository; _readStore = readStore; + _currentUser = currentUser; } public Guid PublishLink(PublishLinkCommand cmd) { - var link = new Link(cmd.Url, cmd.Description); + var link = new Link(_currentUser.GetCurrentUserId(), + cmd.Url, + cmd.Description); _repository.Add(link); @@ -29,7 +34,7 @@ public class LinkService public Guid PublishComment(PublishCommentCommand cmd) { - var comment = new Comment(cmd.LinkId, cmd.Content); + var comment = new Comment(_currentUser.GetCurrentUserId(), cmd.LinkId, cmd.Content); _commentRepository.Add(comment); @@ -53,17 +58,24 @@ public interface IReadStore LinkComment[] GetLinkComments(Guid linkId); } +public interface ICurrentUser +{ + string GetCurrentUserId(); +} + public class LinkHomePage { public Guid Id { get; set; } public string Url { get; set; } public string Description { get; set; } public int CommentsCount { get; set; } + public string Author { get; set; } } public class LinkComment { public string Content { get; set; } + public string User { get; set; } } public class PublishLinkCommand diff --git a/HackerNet.Domain/Comment.cs b/HackerNet.Domain/Comment.cs index 418a6f6..123e176 100644 --- a/HackerNet.Domain/Comment.cs +++ b/HackerNet.Domain/Comment.cs @@ -6,12 +6,14 @@ public class Comment public Guid LinkId { get; } public string Content { get; } public DateTime CreatedAt { get; } + public string CreatedBy { get; } - public Comment(Guid linkId, string content) + public Comment(string createdBy, Guid linkId, string content) { Id = Guid.NewGuid(); LinkId = linkId; Content = content; CreatedAt = DateTime.UtcNow; + CreatedBy = createdBy; } } \ No newline at end of file diff --git a/HackerNet.Domain/Link.cs b/HackerNet.Domain/Link.cs index f85df2a..724bbac 100644 --- a/HackerNet.Domain/Link.cs +++ b/HackerNet.Domain/Link.cs @@ -6,12 +6,14 @@ public class Link public string Url { get; } public string Description { get; } public DateTime CreatedAt { get; } + public string CreatedBy { get; } - public Link(string url, string description) + public Link(string createdBy, string url, string description) { Id = Guid.NewGuid(); Url = url; Description = description; CreatedAt = DateTime.UtcNow; + CreatedBy = createdBy; } } diff --git a/HackerNet.Infrastructure/AspNet/Extensions.cs b/HackerNet.Infrastructure/AspNet/Extensions.cs index 9332568..f49c5b4 100644 --- a/HackerNet.Infrastructure/AspNet/Extensions.cs +++ b/HackerNet.Infrastructure/AspNet/Extensions.cs @@ -13,8 +13,8 @@ public static class ServiceCollectionExtensions { public static IServiceCollection AddHackerNetServicesMemory(this IServiceCollection services) { - var link = new Link("https://localhost:7050/", "Youhouuu"); - var comment = new Comment(link.Id, "Wow!"); + var link = new Link("", "https://localhost:7050/", "Youhouuu"); + var comment = new Comment("", link.Id, "Wow!"); var linksRepository = new MemoryLinkRepository(link); var commentsRepository = new MemoryCommentRepository(comment); @@ -33,6 +33,7 @@ public static class ServiceCollectionExtensions services.AddDbContext(options => options.UseSqlite(configuration.GetConnectionString("Default"))); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/HackerNet.Infrastructure/AspNet/HttpCurrentUser.cs b/HackerNet.Infrastructure/AspNet/HttpCurrentUser.cs new file mode 100644 index 0000000..d1130af --- /dev/null +++ b/HackerNet.Infrastructure/AspNet/HttpCurrentUser.cs @@ -0,0 +1,23 @@ +using HackerNet.Application; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; + +namespace HackerNet.Infrastructure.AspNet; + +public class HttpCurrentUser : ICurrentUser +{ + private readonly IHttpContextAccessor _contextAccessor; + private readonly UserManager _userManager; + + public HttpCurrentUser(IHttpContextAccessor contextAccessor, UserManager userManager) + { + _contextAccessor = contextAccessor; + _userManager = userManager; + } + + public string GetCurrentUserId() + { + var userPrincipal = _contextAccessor.HttpContext.User; + return _userManager.GetUserId(userPrincipal); + } +} \ No newline at end of file diff --git a/HackerNet.Infrastructure/Migrations/20211215145254_AddCreatedBy.Designer.cs b/HackerNet.Infrastructure/Migrations/20211215145254_AddCreatedBy.Designer.cs new file mode 100644 index 0000000..9138e0a --- /dev/null +++ b/HackerNet.Infrastructure/Migrations/20211215145254_AddCreatedBy.Designer.cs @@ -0,0 +1,349 @@ +// +using System; +using HackerNet.Infrastructure.Repositories.EntityFramework; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace HackerNet.Infrastructure.Migrations +{ + [DbContext(typeof(HackerContext))] + [Migration("20211215145254_AddCreatedBy")] + partial class AddCreatedBy + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.1"); + + modelBuilder.Entity("HackerNet.Domain.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("LinkId"); + + b.ToTable("Comments"); + }); + + modelBuilder.Entity("HackerNet.Domain.Link", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.ToTable("Links"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("HackerNet.Domain.Comment", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("HackerNet.Domain.Link", null) + .WithMany() + .HasForeignKey("LinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HackerNet.Domain.Link", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/HackerNet.Infrastructure/Migrations/20211215145254_AddCreatedBy.cs b/HackerNet.Infrastructure/Migrations/20211215145254_AddCreatedBy.cs new file mode 100644 index 0000000..8947873 --- /dev/null +++ b/HackerNet.Infrastructure/Migrations/20211215145254_AddCreatedBy.cs @@ -0,0 +1,81 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace HackerNet.Infrastructure.Migrations +{ + public partial class AddCreatedBy : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("DELETE FROM Links; DELETE FROM Comments;"); + + migrationBuilder.AddColumn( + name: "CreatedBy", + table: "Links", + type: "TEXT", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "CreatedBy", + table: "Comments", + type: "TEXT", + nullable: false, + defaultValue: ""); + + migrationBuilder.CreateIndex( + name: "IX_Links_CreatedBy", + table: "Links", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_Comments_CreatedBy", + table: "Comments", + column: "CreatedBy"); + + migrationBuilder.AddForeignKey( + name: "FK_Comments_AspNetUsers_CreatedBy", + table: "Comments", + column: "CreatedBy", + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Links_AspNetUsers_CreatedBy", + table: "Links", + column: "CreatedBy", + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Comments_AspNetUsers_CreatedBy", + table: "Comments"); + + migrationBuilder.DropForeignKey( + name: "FK_Links_AspNetUsers_CreatedBy", + table: "Links"); + + migrationBuilder.DropIndex( + name: "IX_Links_CreatedBy", + table: "Links"); + + migrationBuilder.DropIndex( + name: "IX_Comments_CreatedBy", + table: "Comments"); + + migrationBuilder.DropColumn( + name: "CreatedBy", + table: "Links"); + + migrationBuilder.DropColumn( + name: "CreatedBy", + table: "Comments"); + } + } +} diff --git a/HackerNet.Infrastructure/Migrations/HackerContextModelSnapshot.cs b/HackerNet.Infrastructure/Migrations/HackerContextModelSnapshot.cs index 405e03a..e79d37a 100644 --- a/HackerNet.Infrastructure/Migrations/HackerContextModelSnapshot.cs +++ b/HackerNet.Infrastructure/Migrations/HackerContextModelSnapshot.cs @@ -30,11 +30,17 @@ namespace HackerNet.Infrastructure.Migrations b.Property("CreatedAt") .HasColumnType("TEXT"); + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("TEXT"); + b.Property("LinkId") .HasColumnType("TEXT"); b.HasKey("Id"); + b.HasIndex("CreatedBy"); + b.HasIndex("LinkId"); b.ToTable("Comments"); @@ -49,6 +55,10 @@ namespace HackerNet.Infrastructure.Migrations b.Property("CreatedAt") .HasColumnType("TEXT"); + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("TEXT"); + b.Property("Description") .IsRequired() .HasColumnType("TEXT"); @@ -60,6 +70,8 @@ namespace HackerNet.Infrastructure.Migrations b.HasKey("Id"); + b.HasIndex("CreatedBy"); + b.ToTable("Links"); }); @@ -257,6 +269,12 @@ namespace HackerNet.Infrastructure.Migrations modelBuilder.Entity("HackerNet.Domain.Comment", b => { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + b.HasOne("HackerNet.Domain.Link", null) .WithMany() .HasForeignKey("LinkId") @@ -264,6 +282,15 @@ namespace HackerNet.Infrastructure.Migrations .IsRequired(); }); + modelBuilder.Entity("HackerNet.Domain.Link", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) diff --git a/HackerNet.Infrastructure/Repositories/EntityFramework/EFReadStore.cs b/HackerNet.Infrastructure/Repositories/EntityFramework/EFReadStore.cs index 2b3fa09..88c1684 100644 --- a/HackerNet.Infrastructure/Repositories/EntityFramework/EFReadStore.cs +++ b/HackerNet.Infrastructure/Repositories/EntityFramework/EFReadStore.cs @@ -27,16 +27,30 @@ public class EFReadStore : IReadStore private IQueryable GetLinks(Guid? id = null) { - return _context.Links - .Where(l => !id.HasValue || l.Id == id) - .OrderByDescending(l => l.CreatedAt) - .Select(l => new LinkHomePage - { - Id = l.Id, - Url = l.Url, - Description = l.Description, - CommentsCount = _context.Comments - .Count(c => c.LinkId == l.Id), - }); + return (from link in _context.Links + join user in _context.Users on link.CreatedBy equals user.Id + orderby link.CreatedAt descending + select new LinkHomePage + { + Id = link.Id, + Url = link.Url, + Description = link.Description, + CommentsCount = _context.Comments.Count(c => c.LinkId == link.Id), + Author = user.UserName, + }); + + + // return _context.Links + // .Where(l => !id.HasValue || l.Id == id) + // .OrderByDescending(l => l.CreatedAt) + // .Join() + // .Select(l => new LinkHomePage + // { + // Id = l.Id, + // Url = l.Url, + // Description = l.Description, + // CommentsCount = _context.Comments + // .Count(c => c.LinkId == l.Id), + // }); } } \ No newline at end of file diff --git a/HackerNet.Infrastructure/Repositories/EntityFramework/EntityTypeConfigurations/CommentEntityTypeConfiguration.cs b/HackerNet.Infrastructure/Repositories/EntityFramework/EntityTypeConfigurations/CommentEntityTypeConfiguration.cs index 7792ce6..be5c2b9 100644 --- a/HackerNet.Infrastructure/Repositories/EntityFramework/EntityTypeConfigurations/CommentEntityTypeConfiguration.cs +++ b/HackerNet.Infrastructure/Repositories/EntityFramework/EntityTypeConfigurations/CommentEntityTypeConfiguration.cs @@ -1,4 +1,5 @@ using HackerNet.Domain; +using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -16,5 +17,10 @@ public class CommentEntityTypeConfiguration : IEntityTypeConfiguration .OnDelete(DeleteBehavior.Cascade); builder.Property(o => o.Content).IsRequired(); builder.Property(o => o.CreatedAt).IsRequired(); + builder.HasOne() + .WithMany() + .HasForeignKey(o => o.CreatedBy) + .IsRequired() + .OnDelete(DeleteBehavior.Cascade); } } \ No newline at end of file diff --git a/HackerNet.Infrastructure/Repositories/EntityFramework/EntityTypeConfigurations/LinkEntityTypeConfiguration.cs b/HackerNet.Infrastructure/Repositories/EntityFramework/EntityTypeConfigurations/LinkEntityTypeConfiguration.cs index 8c26369..7728708 100644 --- a/HackerNet.Infrastructure/Repositories/EntityFramework/EntityTypeConfigurations/LinkEntityTypeConfiguration.cs +++ b/HackerNet.Infrastructure/Repositories/EntityFramework/EntityTypeConfigurations/LinkEntityTypeConfiguration.cs @@ -1,4 +1,5 @@ using HackerNet.Domain; +using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -12,5 +13,11 @@ public class LinkEntityTypeConfiguration : IEntityTypeConfiguration builder.Property(o => o.Url).IsRequired().HasMaxLength(250); builder.Property(o => o.Description).IsRequired(); builder.Property(o => o.CreatedAt).IsRequired(); + + builder.HasOne() + .WithMany() + .HasForeignKey(o => o.CreatedBy) + .IsRequired() + .OnDelete(DeleteBehavior.Cascade); } } \ No newline at end of file