add vote for comments and listing
This commit is contained in:
parent
66cc78d30e
commit
5e3fc3f236
@ -8,9 +8,9 @@ namespace HN.Application
|
|||||||
{
|
{
|
||||||
public sealed class GetLinkQueryHandler : IRequestHandler<GetLinkQuery, LinkDto>
|
public sealed class GetLinkQueryHandler : IRequestHandler<GetLinkQuery, LinkDto>
|
||||||
{
|
{
|
||||||
private readonly IDbContext _context;
|
private readonly IHNContext _context;
|
||||||
|
|
||||||
public GetLinkQueryHandler(IDbContext context)
|
public GetLinkQueryHandler(IHNContext context)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|||||||
18
Application/GetLinkComments/CommentDto.cs
Normal file
18
Application/GetLinkComments/CommentDto.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace HN.Application
|
||||||
|
{
|
||||||
|
public sealed class CommentDto
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string Content { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
public int UpVotes { get; set; }
|
||||||
|
public int DownVotes { get; set; }
|
||||||
|
|
||||||
|
public CommentDto()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Application/GetLinkComments/GetLinkCommentsQuery.cs
Normal file
17
Application/GetLinkComments/GetLinkCommentsQuery.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace HN.Application
|
||||||
|
{
|
||||||
|
public sealed class GetLinkCommentsQuery : IRequest<CommentDto[]>
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public Guid LinkId { get; set; }
|
||||||
|
|
||||||
|
public GetLinkCommentsQuery(Guid linkId)
|
||||||
|
{
|
||||||
|
LinkId = linkId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
Application/GetLinkComments/GetLinkCommentsQueryHandler.cs
Normal file
34
Application/GetLinkComments/GetLinkCommentsQueryHandler.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using HN.Domain;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace HN.Application
|
||||||
|
{
|
||||||
|
public sealed class GetLinkCommentsQueryHandler : IRequestHandler<GetLinkCommentsQuery, CommentDto[]>
|
||||||
|
{
|
||||||
|
private readonly IHNContext _context;
|
||||||
|
|
||||||
|
public GetLinkCommentsQueryHandler(IHNContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<CommentDto[]> Handle(GetLinkCommentsQuery request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var comments = from comment in _context.Comments
|
||||||
|
where comment.LinkId == request.LinkId
|
||||||
|
select new CommentDto
|
||||||
|
{
|
||||||
|
Id = comment.Id,
|
||||||
|
CreatedAt = comment.CreatedAt,
|
||||||
|
Content = comment.Content,
|
||||||
|
UpVotes = comment.Votes.Count(c => c.Type == VoteType.Up),
|
||||||
|
DownVotes = comment.Votes.Count(c => c.Type == VoteType.Down)
|
||||||
|
};
|
||||||
|
|
||||||
|
return Task.FromResult(comments.OrderBy(c => c.CreatedAt).ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,8 +6,9 @@ namespace HN.Application
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface permettant l'accès aux DbSet pour toute la partie Query.
|
/// Interface permettant l'accès aux DbSet pour toute la partie Query.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IDbContext
|
public interface IHNContext
|
||||||
{
|
{
|
||||||
DbSet<Link> Links { get; }
|
DbSet<Link> Links { get; }
|
||||||
|
DbSet<Comment> Comments { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -36,9 +36,9 @@ namespace HN.Application
|
|||||||
|
|
||||||
public sealed class ListLinksQueryHandler : IRequestHandler<ListLinksQuery, LinkDto[]>
|
public sealed class ListLinksQueryHandler : IRequestHandler<ListLinksQuery, LinkDto[]>
|
||||||
{
|
{
|
||||||
private readonly IDbContext _context;
|
private readonly IHNContext _context;
|
||||||
|
|
||||||
public ListLinksQueryHandler(IDbContext context)
|
public ListLinksQueryHandler(IHNContext context)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
@ -55,7 +55,7 @@ namespace HN.Application
|
|||||||
DownVotes = link.Votes.Count(v => v.Type == VoteType.Down)
|
DownVotes = link.Votes.Count(v => v.Type == VoteType.Down)
|
||||||
};
|
};
|
||||||
|
|
||||||
return Task.FromResult(links.ToArray());
|
return Task.FromResult(links.OrderByDescending(l => l.CreatedAt).ToArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
22
Application/VoteForComment/VoteForCommentCommand.cs
Normal file
22
Application/VoteForComment/VoteForCommentCommand.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using HN.Domain;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace HN.Application
|
||||||
|
{
|
||||||
|
public sealed class VoteForCommentCommand : IRequest
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public Guid CommentId { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public VoteType Type { get; set; }
|
||||||
|
|
||||||
|
public VoteForCommentCommand(Guid commentId, VoteType type)
|
||||||
|
{
|
||||||
|
CommentId = commentId;
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
Application/VoteForComment/VoteForCommentCommandHandler.cs
Normal file
36
Application/VoteForComment/VoteForCommentCommandHandler.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using HN.Domain;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace HN.Application
|
||||||
|
{
|
||||||
|
public sealed class VoteForCommentCommandHandler : IRequestHandler<VoteForCommentCommand>
|
||||||
|
{
|
||||||
|
private readonly ICommentRepository _commentRepository;
|
||||||
|
|
||||||
|
public VoteForCommentCommandHandler(ICommentRepository commentRepository)
|
||||||
|
{
|
||||||
|
_commentRepository = commentRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Unit> Handle(VoteForCommentCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var comment = await _commentRepository.GetByIdAsync(request.CommentId);
|
||||||
|
|
||||||
|
switch (request.Type)
|
||||||
|
{
|
||||||
|
case VoteType.Up:
|
||||||
|
comment.Upvote();
|
||||||
|
break;
|
||||||
|
case VoteType.Down:
|
||||||
|
comment.Downvote();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _commentRepository.UpdateAsync(comment);
|
||||||
|
|
||||||
|
return Unit.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,6 +3,8 @@ using Website.Models;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using HN.Domain;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Website.Controllers
|
namespace Website.Controllers
|
||||||
{
|
{
|
||||||
@ -21,7 +23,9 @@ namespace Website.Controllers
|
|||||||
{
|
{
|
||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
{
|
{
|
||||||
return View("../Links/Show", new ShowLinkViewModel(await _bus.Send(new GetLinkQuery(command.LinkId)), command));
|
var link = await _bus.Send(new GetLinkQuery(command.LinkId));
|
||||||
|
var comments = await _bus.Send(new GetLinkCommentsQuery(command.LinkId));
|
||||||
|
return View("../Links/Show", new ShowLinkViewModel(link, command, comments));
|
||||||
}
|
}
|
||||||
|
|
||||||
await _bus.Send(command);
|
await _bus.Send(command);
|
||||||
@ -30,5 +34,16 @@ namespace Website.Controllers
|
|||||||
|
|
||||||
return RedirectToAction(nameof(LinksController.Show), "Links", new { id = command.LinkId });
|
return RedirectToAction(nameof(LinksController.Show), "Links", new { id = command.LinkId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("{controller}/{id:guid}/vote")]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Vote(Guid id, VoteType type, string redirectTo)
|
||||||
|
{
|
||||||
|
await _bus.Send(new VoteForCommentCommand(id, type));
|
||||||
|
|
||||||
|
SetFlash($"Comment {type} added!");
|
||||||
|
|
||||||
|
return Redirect(redirectTo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -26,7 +26,9 @@ namespace Website.Controllers
|
|||||||
[HttpGet("{controller}/{id:guid}")]
|
[HttpGet("{controller}/{id:guid}")]
|
||||||
public async Task<IActionResult> Show(Guid id)
|
public async Task<IActionResult> Show(Guid id)
|
||||||
{
|
{
|
||||||
return View(new ShowLinkViewModel(await _bus.Send(new GetLinkQuery(id)), new CommentLinkCommand(id)));
|
var link = await _bus.Send(new GetLinkQuery(id));
|
||||||
|
var comments = await _bus.Send(new GetLinkCommentsQuery(id));
|
||||||
|
return View(new ShowLinkViewModel(link, new CommentLinkCommand(id), comments));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IActionResult Create()
|
public IActionResult Create()
|
||||||
|
|||||||
@ -7,11 +7,13 @@ namespace Website.Models
|
|||||||
public LinkDto Link { get; set; }
|
public LinkDto Link { get; set; }
|
||||||
|
|
||||||
public CommentLinkCommand CommentForm { get; set; }
|
public CommentLinkCommand CommentForm { get; set; }
|
||||||
|
public CommentDto[] Comments { get; set; }
|
||||||
|
|
||||||
public ShowLinkViewModel(LinkDto link, CommentLinkCommand commentForm)
|
public ShowLinkViewModel(LinkDto link, CommentLinkCommand commentForm, CommentDto[] comments)
|
||||||
{
|
{
|
||||||
Link = link;
|
Link = link;
|
||||||
CommentForm = commentForm;
|
CommentForm = commentForm;
|
||||||
|
Comments = comments;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,10 +25,10 @@ namespace Website
|
|||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddDbContext<HNDbContext>(options => options.UseSqlite(Configuration.GetConnectionString("Default")));
|
services.AddDbContext<HNDbContext>(options => options.UseSqlite(Configuration.GetConnectionString("Default")));
|
||||||
services.AddScoped<IDbContext, HNDbContext>();
|
services.AddScoped<IHNContext, HNDbContext>();
|
||||||
services.AddScoped<ILinkRepository, LinkRepository>();
|
services.AddScoped<ILinkRepository, LinkRepository>();
|
||||||
services.AddScoped<ICommentRepository, CommentRepository>();
|
services.AddScoped<ICommentRepository, CommentRepository>();
|
||||||
services.AddMediatR(typeof(HN.Application.IDbContext));
|
services.AddMediatR(typeof(HN.Application.IHNContext));
|
||||||
|
|
||||||
services.Configure<RouteOptions>(options =>
|
services.Configure<RouteOptions>(options =>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,4 +1,16 @@
|
|||||||
@model ShowLinkViewModel
|
@model ShowLinkViewModel
|
||||||
|
|
||||||
<partial name="_LinkItem" model="@Model.Link" />
|
<partial name="_LinkItem" model="@Model.Link" />
|
||||||
|
|
||||||
|
@if(Model.Comments.Length == 0) {
|
||||||
|
<p>No comments yet</p>
|
||||||
|
} else {
|
||||||
|
<ul>
|
||||||
|
@foreach (var comment in Model.Comments)
|
||||||
|
{
|
||||||
|
<li><partial name="_CommentItem" model="@comment" /></li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
|
||||||
<partial name="_CommentForm" model="@Model.CommentForm" />
|
<partial name="_CommentForm" model="@Model.CommentForm" />
|
||||||
11
Apps/Website/Views/Shared/_CommentItem.cshtml
Normal file
11
Apps/Website/Views/Shared/_CommentItem.cshtml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
@model HN.Application.CommentDto
|
||||||
|
|
||||||
|
<span>@Model.Content</span>
|
||||||
|
<div>
|
||||||
|
👍: @Model.UpVotes / 👎: @Model.DownVotes
|
||||||
|
</div>
|
||||||
|
<form asp-action="Vote" asp-controller="Comments" asp-route-id="@Model.Id" method="post">
|
||||||
|
<input type="hidden" name="redirectTo" value="@Context.Request.Path" />
|
||||||
|
<input type="submit" name="type" value="up" />
|
||||||
|
<input type="submit" name="type" value="down" />
|
||||||
|
</form>
|
||||||
@ -4,6 +4,6 @@
|
|||||||
|
|
||||||
<form asp-controller="Links" asp-action="Vote" asp-route-id="@Model.Id" asp-route-url="@Model.Url" method="post">
|
<form asp-controller="Links" asp-action="Vote" asp-route-id="@Model.Id" asp-route-url="@Model.Url" method="post">
|
||||||
<input type="hidden" name="redirectTo" value="@Context.Request.Path" />
|
<input type="hidden" name="redirectTo" value="@Context.Request.Path" />
|
||||||
<input type="submit" name="type" value="up">👍</button>
|
<input type="submit" name="type" value="up" />
|
||||||
<input type="submit" name="type" value="down">👎</button>
|
<input type="submit" name="type" value="down" />
|
||||||
</form>
|
</form>
|
||||||
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace HN.Domain
|
namespace HN.Domain
|
||||||
{
|
{
|
||||||
@ -8,6 +9,8 @@ namespace HN.Domain
|
|||||||
public Guid LinkId { get; private set; }
|
public Guid LinkId { get; private set; }
|
||||||
public string Content { get; private set; }
|
public string Content { get; private set; }
|
||||||
public DateTime CreatedAt { get; private set; }
|
public DateTime CreatedAt { get; private set; }
|
||||||
|
private List<Vote> _votes;
|
||||||
|
public IReadOnlyList<Vote> Votes => _votes;
|
||||||
|
|
||||||
internal Comment(Guid linkId, string content)
|
internal Comment(Guid linkId, string content)
|
||||||
{
|
{
|
||||||
@ -15,6 +18,17 @@ namespace HN.Domain
|
|||||||
LinkId = linkId;
|
LinkId = linkId;
|
||||||
Content = content;
|
Content = content;
|
||||||
CreatedAt = DateTime.UtcNow;
|
CreatedAt = DateTime.UtcNow;
|
||||||
|
_votes = new List<Vote>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Upvote()
|
||||||
|
{
|
||||||
|
_votes.Add(new Vote(VoteType.Up));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Downvote()
|
||||||
|
{
|
||||||
|
_votes.Add(new Vote(VoteType.Down));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace HN.Domain
|
namespace HN.Domain
|
||||||
@ -5,5 +6,7 @@ namespace HN.Domain
|
|||||||
public interface ICommentRepository
|
public interface ICommentRepository
|
||||||
{
|
{
|
||||||
Task AddAsync(Comment comment);
|
Task AddAsync(Comment comment);
|
||||||
|
Task UpdateAsync(Comment comment);
|
||||||
|
Task<Comment> GetByIdAsync(Guid id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -7,7 +7,7 @@ namespace HN.Domain
|
|||||||
public VoteType Type { get; private set; }
|
public VoteType Type { get; private set; }
|
||||||
public DateTime CreatedAt { get; private set; }
|
public DateTime CreatedAt { get; private set; }
|
||||||
|
|
||||||
public Vote(VoteType type)
|
internal Vote(VoteType type)
|
||||||
{
|
{
|
||||||
Type = type;
|
Type = type;
|
||||||
CreatedAt = DateTime.UtcNow;
|
CreatedAt = DateTime.UtcNow;
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using HN.Domain;
|
using HN.Domain;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace HN.Infrastructure
|
namespace HN.Infrastructure
|
||||||
{
|
{
|
||||||
@ -13,5 +15,15 @@ namespace HN.Infrastructure
|
|||||||
{
|
{
|
||||||
return base.AddAsync(comment);
|
return base.AddAsync(comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<Comment> GetByIdAsync(Guid id)
|
||||||
|
{
|
||||||
|
return Entries.SingleOrDefaultAsync(o => o.Id == id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task UpdateAsync(Comment comment)
|
||||||
|
{
|
||||||
|
return base.UpdateAsync(comment);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -16,6 +16,15 @@ namespace HN.Infrastructure
|
|||||||
.IsRequired();
|
.IsRequired();
|
||||||
builder.Property(o => o.Content).IsRequired();
|
builder.Property(o => o.Content).IsRequired();
|
||||||
builder.Property(o => o.CreatedAt).IsRequired();
|
builder.Property(o => o.CreatedAt).IsRequired();
|
||||||
|
|
||||||
|
builder.OwnsMany(o => o.Votes, vote =>
|
||||||
|
{
|
||||||
|
vote.ToTable("comment_votes");
|
||||||
|
vote.WithOwner().HasForeignKey("CommentId");
|
||||||
|
vote.HasKey("CommentId");
|
||||||
|
vote.Property(o => o.Type).IsRequired();
|
||||||
|
vote.Property(o => o.CreatedAt).IsRequired();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,10 +5,11 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace HN.Infrastructure
|
namespace HN.Infrastructure
|
||||||
{
|
{
|
||||||
public sealed class HNDbContext : DbContext, IDbContext
|
public sealed class HNDbContext : DbContext, IHNContext
|
||||||
{
|
{
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
public DbSet<Link> Links { get; set; }
|
public DbSet<Link> Links { get; set; }
|
||||||
|
public DbSet<Comment> Comments { get; set; }
|
||||||
|
|
||||||
public HNDbContext()
|
public HNDbContext()
|
||||||
{
|
{
|
||||||
|
|||||||
122
Infrastructure/Migrations/20201211102901_CreateCommentVote.Designer.cs
generated
Normal file
122
Infrastructure/Migrations/20201211102901_CreateCommentVote.Designer.cs
generated
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using HN.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
namespace Infrastructure.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(HNDbContext))]
|
||||||
|
[Migration("20201211102901_CreateCommentVote")]
|
||||||
|
partial class CreateCommentVote
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "5.0.0");
|
||||||
|
|
||||||
|
modelBuilder.Entity("HN.Domain.Comment", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Content")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("LinkId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("LinkId");
|
||||||
|
|
||||||
|
b.ToTable("comments");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("HN.Domain.Link", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Url")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("links");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("HN.Domain.Comment", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("HN.Domain.Link", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("LinkId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.OwnsMany("HN.Domain.Vote", "Votes", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<Guid>("CommentId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b1.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b1.Property<int>("Type")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b1.HasKey("CommentId");
|
||||||
|
|
||||||
|
b1.ToTable("comment_votes");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("CommentId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("Votes");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("HN.Domain.Link", b =>
|
||||||
|
{
|
||||||
|
b.OwnsMany("HN.Domain.Vote", "Votes", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<Guid>("LinkId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b1.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b1.Property<int>("Type")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b1.HasKey("LinkId");
|
||||||
|
|
||||||
|
b1.ToTable("link_votes");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("LinkId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("Votes");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace Infrastructure.Migrations
|
||||||
|
{
|
||||||
|
public partial class CreateCommentVote : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "comment_votes",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
CommentId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||||
|
Type = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_comment_votes", x => x.CommentId);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_comment_votes_comments_CommentId",
|
||||||
|
column: x => x.CommentId,
|
||||||
|
principalTable: "comments",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "comment_votes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -61,22 +61,6 @@ namespace Infrastructure.Migrations
|
|||||||
b.ToTable("links");
|
b.ToTable("links");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("HN.Domain.Vote", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("LinkId")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAt")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int>("Type")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasKey("LinkId");
|
|
||||||
|
|
||||||
b.ToTable("link_votes");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("HN.Domain.Comment", b =>
|
modelBuilder.Entity("HN.Domain.Comment", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("HN.Domain.Link", null)
|
b.HasOne("HN.Domain.Link", null)
|
||||||
@ -84,19 +68,50 @@ namespace Infrastructure.Migrations
|
|||||||
.HasForeignKey("LinkId")
|
.HasForeignKey("LinkId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("HN.Domain.Vote", b =>
|
b.OwnsMany("HN.Domain.Vote", "Votes", b1 =>
|
||||||
{
|
{
|
||||||
b.HasOne("HN.Domain.Link", null)
|
b1.Property<Guid>("CommentId")
|
||||||
.WithMany("Votes")
|
.HasColumnType("TEXT");
|
||||||
.HasForeignKey("LinkId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
b1.Property<DateTime>("CreatedAt")
|
||||||
.IsRequired();
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b1.Property<int>("Type")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b1.HasKey("CommentId");
|
||||||
|
|
||||||
|
b1.ToTable("comment_votes");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("CommentId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("Votes");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("HN.Domain.Link", b =>
|
modelBuilder.Entity("HN.Domain.Link", b =>
|
||||||
{
|
{
|
||||||
|
b.OwnsMany("HN.Domain.Vote", "Votes", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<Guid>("LinkId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b1.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b1.Property<int>("Type")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b1.HasKey("LinkId");
|
||||||
|
|
||||||
|
b1.ToTable("link_votes");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("LinkId");
|
||||||
|
});
|
||||||
|
|
||||||
b.Navigation("Votes");
|
b.Navigation("Votes");
|
||||||
});
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
|
|||||||
33
README.md
33
README.md
@ -2,12 +2,41 @@
|
|||||||
|
|
||||||
Le but est de réaliser de A à Z une application Web imitant le fonctionnement de Hacker News en abordant des bonnes pratiques de développement en .Net.
|
Le but est de réaliser de A à Z une application Web imitant le fonctionnement de Hacker News en abordant des bonnes pratiques de développement en .Net.
|
||||||
|
|
||||||
|
## Plan de la formation
|
||||||
|
|
||||||
|
- Présentation d'ASP.Net Core
|
||||||
|
- Mise en place du domaine (Création de librairies de classes)
|
||||||
|
- Mise en place d'une couche applicative découplée avec MediatR
|
||||||
|
- Persistence des données avec EF Core
|
||||||
|
- Aperçu de l'outil
|
||||||
|
- Définir le modèle grâce à l'API Fluent
|
||||||
|
- Gérer les migrations
|
||||||
|
- Réalisation d'un projet MVC
|
||||||
|
- Rappels sur l'architecture MVC
|
||||||
|
- Injection de dépendances
|
||||||
|
- Afficher une page et naviguer entre les pages
|
||||||
|
- Gestion des formulaires
|
||||||
|
- Routage conventionnel / par attributs
|
||||||
|
- Découpage des vues grâce aux vues partielles et composants
|
||||||
|
- Utilisation de TempData pour les messages de statut
|
||||||
|
- Authentification et autorisation
|
||||||
|
- Déploiement
|
||||||
|
- Réalisation d'une API Rest
|
||||||
|
- Rappels sur l'architecture REST
|
||||||
|
- Exposer nos cas d'utilisations au travers de ressources
|
||||||
|
- Documenter l'API
|
||||||
|
- Réalisation d'un client en Blazor
|
||||||
|
- Présentation
|
||||||
|
- Découpage en composants
|
||||||
|
- Gestion des formulaires
|
||||||
|
|
||||||
## Ressources utiles
|
## Ressources utiles
|
||||||
|
|
||||||
- https://aspnetcore.readthedocs.io/en/stable/mvc/index.html
|
- https://aspnetcore.readthedocs.io/en/stable/mvc/index.html
|
||||||
- https://andrewlock.net/an-introduction-to-viewcomponents-a-login-status-view-component/
|
- https://andrewlock.net/an-introduction-to-viewcomponents-a-login-status-view-component/
|
||||||
- https://stackoverflow.com/a/47011478
|
- https://stackoverflow.com/a/47011478
|
||||||
- https://stackoverflow.com/a/34291650
|
- https://stackoverflow.com/a/34291650
|
||||||
|
- https://chrissainty.com/securing-your-blazor-apps-authentication-with-clientside-blazor-using-webapi-aspnet-core-identity/
|
||||||
|
|
||||||
## Commençer par le domaine !
|
## Commençer par le domaine !
|
||||||
|
|
||||||
@ -19,6 +48,10 @@ Mise en place de la couche applicative avec `MediatR` et implémentation du prem
|
|||||||
|
|
||||||
## Le site internet en MVC
|
## Le site internet en MVC
|
||||||
|
|
||||||
|
### Présentation de l'architecture du projet de base
|
||||||
|
|
||||||
|
Aborder l'injection de dépendances et le fichier `Startup.cs` en général.
|
||||||
|
|
||||||
### Options pattern
|
### Options pattern
|
||||||
|
|
||||||
Permet d'avoir des objets de configuration facilement sortable depuis l'appsettings. Utilisation de https://docs.microsoft.com/fr-fr/aspnet/core/fundamentals/configuration/options?view=aspnetcore-5.0.
|
Permet d'avoir des objets de configuration facilement sortable depuis l'appsettings. Utilisation de https://docs.microsoft.com/fr-fr/aspnet/core/fundamentals/configuration/options?view=aspnetcore-5.0.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user