diff --git a/Chirp/src/Chirp.Core/AuthorService.cs b/Chirp/src/Chirp.Core/AuthorService.cs
index c9bc4457..090f5e1b 100644
--- a/Chirp/src/Chirp.Core/AuthorService.cs
+++ b/Chirp/src/Chirp.Core/AuthorService.cs
@@ -2,7 +2,6 @@
namespace Chirp.Core;
-
public interface IAuthorService
{
public List GetFollows(string username);
@@ -11,13 +10,14 @@ public interface IAuthorService
public bool Unfollow(string currentUser, string userToUnFollow);
public bool MakeFollowersUnfollow(string username);
}
+
public class AuthorService(IAuthorRepository db) : IAuthorService
{
public List GetFollows(string username)
{
return db.GetAuthorFollows(username).Result;
}
-
+
public List GetFollowers(string username)
{
return db.GetAuthorFollowers(username).Result;
@@ -27,12 +27,12 @@ public bool MakeFollowersUnfollow(string username)
{
return db.MakeFollowersUnfollow(username).Result;
}
-
+
public bool Follow(string currentUser, string userToFollow)
{
return db.Follow(currentUser, userToFollow).Result;
}
-
+
public bool Unfollow(string currentUser, string userToUnFollow)
{
return db.UnFollow(currentUser, userToUnFollow).Result;
diff --git a/Chirp/src/Chirp.Core/CheepService.cs b/Chirp/src/Chirp.Core/CheepService.cs
index 37c0a97f..980d8090 100644
--- a/Chirp/src/Chirp.Core/CheepService.cs
+++ b/Chirp/src/Chirp.Core/CheepService.cs
@@ -11,6 +11,10 @@ public interface ICheepService
public int GetAmountOfCheepPages(int pageSize);
public int GetAmountOfCheepPagesFromAuthors(IEnumerable authors, int pageSize);
public bool CreateCheep(CheepDTO cheep);
+ public bool AddCommentToCheep(CommentDTO comment);
+ public int GetCommentAmountOnCheep(int? cheepId);
+ public CheepDTO GetCheepFromId(int cheepId);
+ public List GetCommentsFromCheep(int cheepId);
}
public class CheepService(ICheepRepository db) : ICheepService
@@ -19,8 +23,9 @@ public List GetCheepsByPage(int page, int pageSize)
{
ArgumentOutOfRangeException.ThrowIfNegative(pageSize);
- var result = db.GetCheepsByPage(page, pageSize).Result ?? throw new ArgumentNullException(nameof(db.GetCheepsByPage));
-
+ var result = db.GetCheepsByPage(page, pageSize).Result ??
+ throw new ArgumentNullException(nameof(db.GetCheepsByPage));
+
return result.ToList();
}
@@ -33,7 +38,7 @@ public List GetCheepsFromAuthorByPage(string author, int page, int pag
{
return db.GetCheepsFromAuthorByPage(author, page, pageSize).Result.ToList();
}
-
+
public List GetCheepsFromAuthorsByPage(IEnumerable authors, int page, int pageSize)
{
return db.GetCheepsFromAuthorsByPage(authors, page, pageSize).Result.ToList();
@@ -53,4 +58,24 @@ public bool CreateCheep(CheepDTO cheep)
{
return db.CreateCheep(cheep).Result;
}
+
+ public bool AddCommentToCheep(CommentDTO comment)
+ {
+ return db.AddCommentToCheep(comment).Result;
+ }
+
+ public int GetCommentAmountOnCheep(int? cheepId)
+ {
+ return db.GetCommentAmountOnCheep(cheepId).Result;
+ }
+
+ public CheepDTO GetCheepFromId(int cheepId)
+ {
+ return db.GetCheepById(cheepId).Result;
+ }
+
+ public List GetCommentsFromCheep(int cheepId)
+ {
+ return db.GetCommentsForCheep(cheepId).Result.ToList();
+ }
}
\ No newline at end of file
diff --git a/Chirp/src/Chirp.Core/DTO/CheepDTO.cs b/Chirp/src/Chirp.Core/DTO/CheepDTO.cs
index e3ab9614..84804c8b 100644
--- a/Chirp/src/Chirp.Core/DTO/CheepDTO.cs
+++ b/Chirp/src/Chirp.Core/DTO/CheepDTO.cs
@@ -1,7 +1,8 @@
namespace Chirp.Core.DTO;
-public class CheepDTO(string author, string message, long unixTimestamp)
+public class CheepDTO(int? id, string author, string message, long unixTimestamp)
{
+ public int? Id { get; set; } = id;
public string Author { get; set; } = author;
public string Message { get; set; } = message;
public long UnixTimestamp { get; set; } = unixTimestamp;
diff --git a/Chirp/src/Chirp.Core/DTO/CommentDTO.cs b/Chirp/src/Chirp.Core/DTO/CommentDTO.cs
new file mode 100644
index 00000000..9f4b9296
--- /dev/null
+++ b/Chirp/src/Chirp.Core/DTO/CommentDTO.cs
@@ -0,0 +1,9 @@
+namespace Chirp.Core.DTO;
+
+public class CommentDTO(string author, int cheepId, string message, long unixTimestamp)
+{
+ public string Author { get; set; } = author;
+ public int CheepId { get; set; } = cheepId;
+ public string Message { get; set; } = message;
+ public long UnixTimestamp { get; set; } = unixTimestamp;
+}
\ No newline at end of file
diff --git a/Chirp/src/Chirp.Core/IAuthorRepository.cs b/Chirp/src/Chirp.Core/IAuthorRepository.cs
index 8765441b..4a33404c 100644
--- a/Chirp/src/Chirp.Core/IAuthorRepository.cs
+++ b/Chirp/src/Chirp.Core/IAuthorRepository.cs
@@ -8,7 +8,7 @@ public interface IAuthorRepository
public Task AddAuthor(AuthorDTO author);
public Task> GetAuthorFollows(string username);
public Task> GetAuthorFollowers(string username);
- public Task Follow (string currentUser, string userToFollow);
- public Task UnFollow (string currentUser, string userToUnFollow);
+ public Task Follow(string currentUser, string userToFollow);
+ public Task UnFollow(string currentUser, string userToUnFollow);
public Task MakeFollowersUnfollow(string user);
}
\ No newline at end of file
diff --git a/Chirp/src/Chirp.Core/ICheepRepository.cs b/Chirp/src/Chirp.Core/ICheepRepository.cs
index ffd6b5ef..64f1a97e 100644
--- a/Chirp/src/Chirp.Core/ICheepRepository.cs
+++ b/Chirp/src/Chirp.Core/ICheepRepository.cs
@@ -1,4 +1,5 @@
-using Chirp.Core.DTO;
+using System.Collections;
+using Chirp.Core.DTO;
namespace Chirp.Core;
@@ -11,4 +12,8 @@ public interface ICheepRepository
public Task CreateCheep(CheepDTO cheep);
public Task GetAmountOfCheeps();
public Task GetAmountOfCheepsFromAuthors(IEnumerable authors);
+ public Task AddCommentToCheep(CommentDTO comment);
+ public Task GetCommentAmountOnCheep(int? cheepId);
+ public Task GetCheepById(int cheepId);
+ public Task> GetCommentsForCheep(int cheepId);
}
\ No newline at end of file
diff --git a/Chirp/src/Chirp.Infrastructure/AuthorRepository.cs b/Chirp/src/Chirp.Infrastructure/AuthorRepository.cs
index db43ed29..7fbb741d 100644
--- a/Chirp/src/Chirp.Infrastructure/AuthorRepository.cs
+++ b/Chirp/src/Chirp.Infrastructure/AuthorRepository.cs
@@ -43,7 +43,7 @@ public async Task> GetAuthorFollowers(string username)
.Where(a => a.NormalizedUserName == username.ToUpper())
.Select(a => a.Followers)
.FirstOrDefaultAsync() ?? new List();
-
+
return followers.Select(a => new AuthorDTO(a.UserName!, a.Email!)).ToList();
}
@@ -53,10 +53,10 @@ public async Task Follow(string currentUser, string userToFollow)
if (!await UserExists(currentUser)) throw new UserDoesNotExist();
if (!await UserExists(userToFollow)) throw new UserDoesNotExist("User to follow does not exist");
if (await DoesFollow(currentUser, userToFollow)) return false;
-
+
var authorToFollow = await GetAuthor(userToFollow);
GetAuthor(currentUser).Result.Following.Add(authorToFollow);
-
+
await context.SaveChangesAsync();
return true;
}
@@ -69,17 +69,17 @@ public async Task UnFollow(string currentUser, string userToUnfollow)
if (!await UserExists(currentUser)) throw new UserDoesNotExist();
if (!await UserExists(userToUnfollow)) throw new UserDoesNotExist("User to unfollow does not exist");
if (!await DoesFollow(currentUser, userToUnfollow)) return false;
-
+
var authorToUnfollow = await GetAuthor(userToUnfollow);
context.Authors
.Where(a => a.NormalizedUserName == currentUser)
.Include(a => a.Following)
.FirstOrDefault()!.Following.Remove(authorToUnfollow);
-
+
await context.SaveChangesAsync();
return true;
}
-
+
private async Task GetAuthor(string username)
{
username = username.ToUpper();
@@ -98,14 +98,14 @@ private async Task DoesFollow(string currentUser, string userToFollow)
{
currentUser = currentUser.ToUpper();
userToFollow = userToFollow.ToUpper();
- var list = await context.Authors
- .Where(a => a.NormalizedUserName == currentUser)
- .Select(a => a.Following).FirstOrDefaultAsync();
-
- return list != null && list.Any(a => a.NormalizedUserName == userToFollow);
+ var list = await context.Authors
+ .Where(a => a.NormalizedUserName == currentUser)
+ .Select(a => a.Following).FirstOrDefaultAsync();
+
+ return list != null && list.Any(a => a.NormalizedUserName == userToFollow);
}
- private async Task> GetFollowing(string currentUser)
+ private async Task> GetFollowing(string currentUser)
{
currentUser = currentUser.ToUpper();
return await context.Authors
diff --git a/Chirp/src/Chirp.Infrastructure/CheepRepository.cs b/Chirp/src/Chirp.Infrastructure/CheepRepository.cs
index ab88a386..a47390fe 100644
--- a/Chirp/src/Chirp.Infrastructure/CheepRepository.cs
+++ b/Chirp/src/Chirp.Infrastructure/CheepRepository.cs
@@ -12,9 +12,9 @@ public class CheepRepository(ChirpDBContext context) : ICheepRepository
public async Task?> GetCheepsByPage(int page, int pageSize)
{
if (pageSize < 0) return null;
-
+
var query = context.Cheeps
- .Select(cheep => new { cheep.Author.UserName, cheep.Message, cheep.TimeStamp })
+ .Select(cheep => new { cheep.Id, cheep.Author.UserName, cheep.Message, cheep.TimeStamp })
.OrderByDescending(cheep => cheep.TimeStamp)
.Skip((page - 1) * pageSize)
.Take(pageSize);
@@ -22,7 +22,8 @@ public class CheepRepository(ChirpDBContext context) : ICheepRepository
var cheeps = await query.ToListAsync();
return cheeps.Select(cheep =>
- new CheepDTO(cheep.UserName!, cheep.Message, new DateTimeOffset(cheep.TimeStamp).ToUnixTimeSeconds()));
+ new CheepDTO(cheep.Id, cheep.UserName!, cheep.Message,
+ new DateTimeOffset(cheep.TimeStamp).ToUnixTimeSeconds()));
}
public async Task> GetCheepsFromAuthorByPage(string author, int page, int pageSize)
@@ -35,20 +36,22 @@ public async Task> GetCheepsFromAuthor(string author)
author = author.ToUpper();
var query = context.Cheeps
.Where(cheep => cheep.Author.NormalizedUserName == author)
- .Select(cheep => new { cheep.Author.UserName, cheep.Message, cheep.TimeStamp })
+ .Select(cheep => new { cheep.Id, cheep.Author.UserName, cheep.Message, cheep.TimeStamp })
.OrderByDescending(cheep => cheep.TimeStamp);
var cheeps = await query.ToListAsync();
return cheeps.Select(cheep =>
- new CheepDTO(cheep.UserName!, cheep.Message, new DateTimeOffset(cheep.TimeStamp).ToUnixTimeSeconds()));
+ new CheepDTO(cheep.Id, cheep.UserName!, cheep.Message,
+ new DateTimeOffset(cheep.TimeStamp).ToUnixTimeSeconds()));
}
-
- public async Task> GetCheepsFromAuthorsByPage(IEnumerable authors, int page, int pageSize)
+
+ public async Task> GetCheepsFromAuthorsByPage(IEnumerable authors, int page,
+ int pageSize)
{
authors = authors.Select(author => author.ToUpper());
var query = context.Cheeps
.Where(cheep => authors.Contains(cheep.Author.NormalizedUserName!))
- .Select(cheep => new { cheep.Author.UserName, cheep.Message, cheep.TimeStamp })
+ .Select(cheep => new { cheep.Id, cheep.Author.UserName, cheep.Message, cheep.TimeStamp })
.OrderByDescending(cheep => cheep.TimeStamp)
.Skip((page - 1) * pageSize)
.Take(pageSize);
@@ -56,7 +59,8 @@ public async Task> GetCheepsFromAuthorsByPage(IEnumerable<
var cheeps = await query.ToListAsync();
return cheeps.Select(cheep =>
- new CheepDTO(cheep.UserName!, cheep.Message, new DateTimeOffset(cheep.TimeStamp).ToUnixTimeSeconds()));
+ new CheepDTO(cheep.Id, cheep.UserName!, cheep.Message,
+ new DateTimeOffset(cheep.TimeStamp).ToUnixTimeSeconds()));
}
public Task GetAmountOfCheeps()
@@ -77,9 +81,70 @@ public async Task CreateCheep(CheepDTO cheep)
.Where(a => a.UserName == cheep.Author)
.FirstOrDefaultAsync();
if (author == null) return false;
- var cheep2 = new Cheep {Author = author, Message = cheep.Message, TimeStamp = DateTimeOffset.FromUnixTimeSeconds(cheep.UnixTimestamp).DateTime};
+ var cheep2 = new Cheep
+ {
+ Author = author, Message = cheep.Message,
+ TimeStamp = DateTimeOffset.FromUnixTimeSeconds(cheep.UnixTimestamp).DateTime
+ };
context.Cheeps.Add(cheep2);
await context.SaveChangesAsync();
return true;
}
+
+ public async Task AddCommentToCheep(CommentDTO comment)
+ {
+ var author = await context.Authors
+ .Where(a => a.UserName == comment.Author)
+ .FirstOrDefaultAsync();
+ if (author == null) return false;
+ var cheep = await context.Cheeps
+ .Where(c => c.Id == comment.CheepId)
+ .Include(c => c.Comments)
+ .FirstOrDefaultAsync();
+ if (cheep == null) return false;
+ var newComment = new Comment
+ {
+ Author = author, Cheep = cheep, Message = comment.Message,
+ TimeStamp = DateTimeOffset.FromUnixTimeSeconds(comment.UnixTimestamp).DateTime
+ };
+ cheep.Comments.Add(newComment);
+ await context.SaveChangesAsync();
+ return true;
+ }
+
+ public async Task GetCommentAmountOnCheep(int? cheepId)
+ {
+ if (cheepId == null) return 0;
+ var query = await context.Cheeps
+ .Include(c => c.Comments)
+ .Where(c => c.Id == cheepId)
+ .FirstOrDefaultAsync();
+ var commentCount = query!.Comments.Count;
+ return commentCount;
+ }
+
+ public async Task GetCheepById(int cheepId)
+ {
+ var cheep = await context.Cheeps
+ .Where(c => c.Id == cheepId)
+ .Select(cheep => new { cheep.Id, cheep.Author.UserName, cheep.Message, cheep.TimeStamp })
+ .FirstOrDefaultAsync();
+
+ return new CheepDTO(cheep!.Id, cheep.UserName!, cheep.Message,
+ new DateTimeOffset(cheep.TimeStamp).ToUnixTimeSeconds());
+ ;
+ }
+
+ public async Task> GetCommentsForCheep(int cheepId)
+ {
+ var query = await context.Cheeps
+ .Where(c => c.Id == cheepId)
+ .Include(c => c.Comments)
+ .ThenInclude(comment => comment.Author)
+ .FirstOrDefaultAsync();
+ var comments = query!.Comments.OrderByDescending(c => c.TimeStamp);
+ return comments.Select(comment =>
+ new CommentDTO(comment.Author.UserName!, comment.Id, comment.Message,
+ new DateTimeOffset(comment.TimeStamp).ToUnixTimeSeconds()));
+ }
}
\ No newline at end of file
diff --git a/Chirp/src/Chirp.Infrastructure/Chirp.Infrastructure.csproj b/Chirp/src/Chirp.Infrastructure/Chirp.Infrastructure.csproj
index 705e30cb..36b90add 100644
--- a/Chirp/src/Chirp.Infrastructure/Chirp.Infrastructure.csproj
+++ b/Chirp/src/Chirp.Infrastructure/Chirp.Infrastructure.csproj
@@ -7,12 +7,12 @@
-
+
-
-
+
+
diff --git a/Chirp/src/Chirp.Infrastructure/ChirpDbContext.cs b/Chirp/src/Chirp.Infrastructure/ChirpDbContext.cs
index 8ce020b9..1915dca4 100644
--- a/Chirp/src/Chirp.Infrastructure/ChirpDbContext.cs
+++ b/Chirp/src/Chirp.Infrastructure/ChirpDbContext.cs
@@ -8,6 +8,7 @@ public class ChirpDBContext(DbContextOptions options) : Identity
{
public DbSet Cheeps { get; set; }
public DbSet Authors { get; set; }
+ public DbSet Comments { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -51,6 +52,27 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.WithMany(a => a.Followers);
});
-
+ //define comment entity constraints
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasKey((c => c.Id));
+ entity.Property(c => c.Id)
+ .ValueGeneratedOnAdd();
+
+ entity.Property(c => c.Message)
+ .IsRequired()
+ .HasMaxLength(160);
+ entity.Property(c => c.TimeStamp)
+ .IsRequired();
+ entity.HasOne(c => c.Author)
+ .WithMany(a => a.Comments)
+ .IsRequired()
+ .OnDelete(DeleteBehavior.Cascade);
+
+ entity.HasOne(c => c.Cheep)
+ .WithMany(c => c.Comments)
+ .IsRequired()
+ .OnDelete(DeleteBehavior.Cascade);
+ });
}
}
\ No newline at end of file
diff --git a/Chirp/src/Chirp.Infrastructure/Migrations/20241127124139_AddedComments.Designer.cs b/Chirp/src/Chirp.Infrastructure/Migrations/20241127124139_AddedComments.Designer.cs
new file mode 100644
index 00000000..a6a3d1df
--- /dev/null
+++ b/Chirp/src/Chirp.Infrastructure/Migrations/20241127124139_AddedComments.Designer.cs
@@ -0,0 +1,406 @@
+//
+using System;
+using Chirp.Infrastructure;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Chirp.Infrastructure.Migrations
+{
+ [DbContext(typeof(ChirpDBContext))]
+ [Migration("20241127124139_AddedComments")]
+ partial class AddedComments
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "8.0.10");
+
+ modelBuilder.Entity("AuthorAuthor", b =>
+ {
+ b.Property("FollowersId")
+ .HasColumnType("TEXT");
+
+ b.Property("FollowingId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("FollowersId", "FollowingId");
+
+ b.HasIndex("FollowingId");
+
+ b.ToTable("AuthorAuthor");
+ });
+
+ modelBuilder.Entity("Chirp.Infrastructure.Model.Author", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Email")
+ .IsRequired()
+ .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")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Email");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex");
+
+ b.HasIndex("UserName")
+ .IsUnique();
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("Chirp.Infrastructure.Model.Cheep", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AuthorId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasMaxLength(160)
+ .HasColumnType("TEXT");
+
+ b.Property("TimeStamp")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AuthorId");
+
+ b.ToTable("Cheeps");
+ });
+
+ modelBuilder.Entity("Chirp.Infrastructure.Model.Comment", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AuthorId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("CheepId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasMaxLength(160)
+ .HasColumnType("TEXT");
+
+ b.Property("TimeStamp")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AuthorId");
+
+ b.HasIndex("CheepId");
+
+ b.ToTable("Comments");
+ });
+
+ 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.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")
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("ProviderKey")
+ .HasMaxLength(128)
+ .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")
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens", (string)null);
+ });
+
+ modelBuilder.Entity("AuthorAuthor", b =>
+ {
+ b.HasOne("Chirp.Infrastructure.Model.Author", null)
+ .WithMany()
+ .HasForeignKey("FollowersId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Chirp.Infrastructure.Model.Author", null)
+ .WithMany()
+ .HasForeignKey("FollowingId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Chirp.Infrastructure.Model.Cheep", b =>
+ {
+ b.HasOne("Chirp.Infrastructure.Model.Author", "Author")
+ .WithMany("Cheeps")
+ .HasForeignKey("AuthorId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Author");
+ });
+
+ modelBuilder.Entity("Chirp.Infrastructure.Model.Comment", b =>
+ {
+ b.HasOne("Chirp.Infrastructure.Model.Author", "Author")
+ .WithMany("Comments")
+ .HasForeignKey("AuthorId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Chirp.Infrastructure.Model.Cheep", "Cheep")
+ .WithMany("Comments")
+ .HasForeignKey("CheepId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Author");
+
+ b.Navigation("Cheep");
+ });
+
+ 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("Chirp.Infrastructure.Model.Author", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("Chirp.Infrastructure.Model.Author", 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("Chirp.Infrastructure.Model.Author", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.HasOne("Chirp.Infrastructure.Model.Author", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Chirp.Infrastructure.Model.Author", b =>
+ {
+ b.Navigation("Cheeps");
+
+ b.Navigation("Comments");
+ });
+
+ modelBuilder.Entity("Chirp.Infrastructure.Model.Cheep", b =>
+ {
+ b.Navigation("Comments");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/Chirp/src/Chirp.Infrastructure/Migrations/20241127124139_AddedComments.cs b/Chirp/src/Chirp.Infrastructure/Migrations/20241127124139_AddedComments.cs
new file mode 100644
index 00000000..05b31ddb
--- /dev/null
+++ b/Chirp/src/Chirp.Infrastructure/Migrations/20241127124139_AddedComments.cs
@@ -0,0 +1,60 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Chirp.Infrastructure.Migrations
+{
+ ///
+ public partial class AddedComments : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "Comments",
+ columns: table => new
+ {
+ Id = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ AuthorId = table.Column(type: "TEXT", nullable: false),
+ CheepId = table.Column(type: "INTEGER", nullable: false),
+ TimeStamp = table.Column(type: "TEXT", nullable: false),
+ Message = table.Column(type: "TEXT", maxLength: 160, nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Comments", x => x.Id);
+ table.ForeignKey(
+ name: "FK_Comments_AspNetUsers_AuthorId",
+ column: x => x.AuthorId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_Comments_Cheeps_CheepId",
+ column: x => x.CheepId,
+ principalTable: "Cheeps",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Comments_AuthorId",
+ table: "Comments",
+ column: "AuthorId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Comments_CheepId",
+ table: "Comments",
+ column: "CheepId");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "Comments");
+ }
+ }
+}
diff --git a/Chirp/src/Chirp.Infrastructure/Migrations/ChirpDBContextModelSnapshot.cs b/Chirp/src/Chirp.Infrastructure/Migrations/ChirpDBContextModelSnapshot.cs
index 886c3472..44c750b3 100644
--- a/Chirp/src/Chirp.Infrastructure/Migrations/ChirpDBContextModelSnapshot.cs
+++ b/Chirp/src/Chirp.Infrastructure/Migrations/ChirpDBContextModelSnapshot.cs
@@ -128,6 +128,36 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.ToTable("Cheeps");
});
+ modelBuilder.Entity("Chirp.Infrastructure.Model.Comment", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AuthorId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("CheepId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasMaxLength(160)
+ .HasColumnType("TEXT");
+
+ b.Property("TimeStamp")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AuthorId");
+
+ b.HasIndex("CheepId");
+
+ b.ToTable("Comments");
+ });
+
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property("Id")
@@ -286,6 +316,25 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Navigation("Author");
});
+ modelBuilder.Entity("Chirp.Infrastructure.Model.Comment", b =>
+ {
+ b.HasOne("Chirp.Infrastructure.Model.Author", "Author")
+ .WithMany("Comments")
+ .HasForeignKey("AuthorId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Chirp.Infrastructure.Model.Cheep", "Cheep")
+ .WithMany("Comments")
+ .HasForeignKey("CheepId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Author");
+
+ b.Navigation("Cheep");
+ });
+
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
@@ -340,6 +389,13 @@ protected override void BuildModel(ModelBuilder modelBuilder)
modelBuilder.Entity("Chirp.Infrastructure.Model.Author", b =>
{
b.Navigation("Cheeps");
+
+ b.Navigation("Comments");
+ });
+
+ modelBuilder.Entity("Chirp.Infrastructure.Model.Cheep", b =>
+ {
+ b.Navigation("Comments");
});
#pragma warning restore 612, 618
}
diff --git a/Chirp/src/Chirp.Infrastructure/Model/Author.cs b/Chirp/src/Chirp.Infrastructure/Model/Author.cs
index 069046d7..a7794260 100644
--- a/Chirp/src/Chirp.Infrastructure/Model/Author.cs
+++ b/Chirp/src/Chirp.Infrastructure/Model/Author.cs
@@ -8,6 +8,7 @@ public sealed class Author : IdentityUser
public List Cheeps { get; set; } = [];
public List Following { get; set; } = [];
public List Followers { get; set; } = [];
+ public List Comments { get; set; } = [];
public Author()
{
diff --git a/Chirp/src/Chirp.Infrastructure/Model/Cheep.cs b/Chirp/src/Chirp.Infrastructure/Model/Cheep.cs
index 38771a42..a305ea57 100644
--- a/Chirp/src/Chirp.Infrastructure/Model/Cheep.cs
+++ b/Chirp/src/Chirp.Infrastructure/Model/Cheep.cs
@@ -4,14 +4,14 @@ namespace Chirp.Infrastructure.Model;
public class Cheep
{
- public int Id { get; set; }
- public required string Message { get; set; }
- public DateTime TimeStamp { get; set; }
+ public int Id { get; set; }
+ public required string Message { get; set; }
+ public DateTime TimeStamp { get; set; }
public required Author Author { get; set; }
-
+ public List Comments { get; set; } = [];
+
public Cheep()
{
-
}
public Cheep(int id, string message, DateTime timeStamp, Author author)
diff --git a/Chirp/src/Chirp.Infrastructure/Model/Comment.cs b/Chirp/src/Chirp.Infrastructure/Model/Comment.cs
new file mode 100644
index 00000000..65d98398
--- /dev/null
+++ b/Chirp/src/Chirp.Infrastructure/Model/Comment.cs
@@ -0,0 +1,23 @@
+namespace Chirp.Infrastructure.Model;
+
+public class Comment
+{
+ public int Id { get; set; }
+ public required Author Author { get; set; }
+ public required Cheep Cheep { get; set; }
+ public DateTime TimeStamp { get; set; }
+ public required string Message { get; set; }
+
+ public Comment()
+ {
+ }
+
+ public Comment(int id, Author author, Cheep cheep, string message, DateTime timeStamp)
+ {
+ Id = id;
+ Author = author;
+ Cheep = cheep;
+ Message = message;
+ TimeStamp = timeStamp;
+ }
+}
\ No newline at end of file
diff --git a/Chirp/src/Chirp.Web/Pages/Shared/Components/CheepList/Default.cshtml b/Chirp/src/Chirp.Web/Pages/Shared/Components/CheepList/Default.cshtml
index 84d01615..cbc3d9dd 100644
--- a/Chirp/src/Chirp.Web/Pages/Shared/Components/CheepList/Default.cshtml
+++ b/Chirp/src/Chirp.Web/Pages/Shared/Components/CheepList/Default.cshtml
@@ -1,6 +1,9 @@
@using Chirp.Core
@using Chirp.Core.DTO
@inject IAuthorService service
+@inject ICheepService CheepService
+
+private
@{
var targetPage = ViewBag.TargetPage as string ?? "";
IEnumerable cheeps = ViewBag.Cheeps;
@@ -11,6 +14,21 @@
}
}
+
+
@foreach (var cheep in cheeps)
{
@@ -32,7 +50,14 @@
@cheep.Message
- — @DateTimeOffset.FromUnixTimeSeconds(cheep.UnixTimestamp).DateTime.ToString("dd/MM/yy H:mm:ss")
+
+
— @DateTimeOffset.FromUnixTimeSeconds(cheep.UnixTimestamp).DateTime.ToString("dd/MM/yy H:mm:ss")
+
+
+
+
@CheepService.GetCommentAmountOnCheep(cheep.Id)
+
}
-
\ No newline at end of file
+
diff --git a/Chirp/src/Chirp.Web/Pages/Shared/TimeLinePageModel.cs b/Chirp/src/Chirp.Web/Pages/Shared/TimeLinePageModel.cs
index b26ef461..24b22295 100644
--- a/Chirp/src/Chirp.Web/Pages/Shared/TimeLinePageModel.cs
+++ b/Chirp/src/Chirp.Web/Pages/Shared/TimeLinePageModel.cs
@@ -16,24 +16,26 @@ public abstract class TimeLinePageModel(ICheepService cheepService) : PageModel
public int LastPageNumber = 1;
protected const int PageSize = 32;
- [BindProperty]
- public MessageModel MessageModel { get; set; } = new MessageModel();
-
+ [BindProperty] public MessageModel MessageModel { get; set; } = new MessageModel();
+
public ActionResult OnPost(string? author, [FromQuery] int page)
{
- if (string.IsNullOrWhiteSpace(MessageModel.Message)) {
+ if (string.IsNullOrWhiteSpace(MessageModel.Message))
+ {
ModelState.AddModelError("Message", "Message cannot be empty");
}
-
- if (!ModelState.IsValid) {
+
+ if (!ModelState.IsValid)
+ {
LoadCheeps(page);
return Page();
}
-
+
var dt = (DateTimeOffset)DateTime.UtcNow;
if (User.Identity != null)
{
- var cheep = new CheepDTO (
+ var cheep = new CheepDTO(
+ null,
User.Identity.Name ?? "no name",
MessageModel.Message!,
dt.ToUnixTimeSeconds()
@@ -49,5 +51,24 @@ public ActionResult OnPost(string? author, [FromQuery] int page)
return RedirectToPage(null); //redirects to the same page
}
+ public IActionResult OnPostComment(string author, int cheepId, string comment)
+ {
+ var dt = DateTimeOffset.UtcNow;
+ var commentDTO = new CommentDTO
+ (
+ author,
+ cheepId,
+ comment,
+ dt.ToUnixTimeSeconds()
+ );
+
+ if (!CheepService.AddCommentToCheep(commentDTO))
+ {
+ throw new ApplicationException("Failed to add comment");
+ }
+
+ return RedirectToPage(null);
+ }
+
protected abstract void LoadCheeps(int page);
}
\ No newline at end of file
diff --git a/Chirp/src/Chirp.Web/Pages/SpecificCheep.cshtml b/Chirp/src/Chirp.Web/Pages/SpecificCheep.cshtml
new file mode 100644
index 00000000..98f8b697
--- /dev/null
+++ b/Chirp/src/Chirp.Web/Pages/SpecificCheep.cshtml
@@ -0,0 +1,60 @@
+@page
+@model Chirp.Web.Pages.SpecificCheep
+
+@{
+ ViewData["Title"] = "Chirp!";
+ Layout = "Shared/_Layout";
+ var cheep = Model.Cheep;
+ var comments = Model.Comments;
+}
+
+
+
+ @cheep.Author
+
+
@cheep.Message
+
+
+
+
@Model.CommentCount
+
+
+ @if (User.Identity!.IsAuthenticated)
+ {
+
+
+
+ }
+ else
+ {
+
Log in to comment on this Cheep
+ }
+
+
+
+ @foreach (var comment in comments)
+ {
+ -
+
+
+ @comment.Author
+
+
-
+
@Model.TimeSinceComment(comment.UnixTimestamp)
+
+
+ @comment.Message
+
+ }
+
+
+
diff --git a/Chirp/src/Chirp.Web/Pages/SpecificCheep.cshtml.cs b/Chirp/src/Chirp.Web/Pages/SpecificCheep.cshtml.cs
new file mode 100644
index 00000000..8d0d116d
--- /dev/null
+++ b/Chirp/src/Chirp.Web/Pages/SpecificCheep.cshtml.cs
@@ -0,0 +1,81 @@
+using Chirp.Core;
+using Chirp.Core.DTO;
+using Chirp.Web.Pages.BindingModels;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace Chirp.Web.Pages;
+
+public class SpecificCheep(ICheepService cheepService) : PageModel
+{
+ public CheepDTO Cheep { get; set; } = null!;
+ public int CommentCount { get; set; }
+ public List Comments { get; set; } = [];
+
+ [BindProperty] public MessageModel MessageModel { get; set; } = new MessageModel();
+
+ public void OnGet(int cheepId)
+ {
+ Cheep = cheepService.GetCheepFromId(cheepId);
+ CommentCount = cheepService.GetCommentAmountOnCheep(cheepId);
+ Comments = cheepService.GetCommentsFromCheep(cheepId);
+ }
+
+ public string TimeSinceComment(long timeStamp)
+ {
+ var utcThen = DateTimeOffset.FromUnixTimeSeconds(timeStamp);
+ var localTimeZone = TimeZoneInfo.Local;
+ var then = TimeZoneInfo.ConvertTime(utcThen, localTimeZone);
+ var now = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, localTimeZone);
+
+ var timesince = (now - then) switch
+ {
+ { TotalMinutes: < 60 } ts => $"{ts.Minutes} minutes ago",
+ { TotalHours: < 24 } ts => $"{ts.Hours} hours ago",
+ { TotalDays: < 2 } => $"yesterday",
+ { TotalDays: < 5 } => $"on {then.DayOfWeek}",
+ var ts => $"{ts.Days} days ago",
+ };
+ return timesince;
+ }
+
+ public IActionResult OnPostComment(string author, int cheepId)
+ {
+ if (string.IsNullOrWhiteSpace(MessageModel.Message))
+ {
+ ModelState.AddModelError("Message", "Message cannot be empty");
+ Reload(cheepId);
+ return Page();
+ }
+
+ if (!ModelState.IsValid)
+ {
+ Reload(cheepId);
+ return Page();
+ }
+
+
+ var dt = DateTimeOffset.UtcNow;
+ var commentDTO = new CommentDTO
+ (
+ author,
+ cheepId,
+ MessageModel.Message,
+ dt.ToUnixTimeSeconds()
+ );
+
+ if (!cheepService.AddCommentToCheep(commentDTO))
+ {
+ throw new ApplicationException("Failed to add comment");
+ }
+
+ return RedirectToPage("/SpecificCheep", new { cheepId = cheepId });
+ }
+
+ private void Reload(int cheepId)
+ {
+ Cheep = cheepService.GetCheepFromId(cheepId);
+ CommentCount = cheepService.GetCommentAmountOnCheep(cheepId);
+ Comments = cheepService.GetCommentsFromCheep(cheepId);
+ }
+}
\ No newline at end of file
diff --git a/Chirp/src/Chirp.Web/wwwroot/images/comment.png b/Chirp/src/Chirp.Web/wwwroot/images/comment.png
new file mode 100644
index 00000000..f974e87e
Binary files /dev/null and b/Chirp/src/Chirp.Web/wwwroot/images/comment.png differ
diff --git a/Chirp/test/PlaywrightTests/UITests/CommentTests.cs b/Chirp/test/PlaywrightTests/UITests/CommentTests.cs
new file mode 100644
index 00000000..16bd9406
--- /dev/null
+++ b/Chirp/test/PlaywrightTests/UITests/CommentTests.cs
@@ -0,0 +1,71 @@
+using Microsoft.Playwright;
+using PlaywrightTests.Utils;
+using PlaywrightTests.Utils.PageTests;
+
+namespace PlaywrightTests.UITests;
+
+public class CommentTests : PageTestWithRazorPlaywrightWebApplicationFactory
+{
+ private TestAuthor _testAuthor;
+
+ [SetUp]
+ public async Task SetUp()
+ {
+ _testAuthor = new TestAuthorBuilder(RazorFactory.GetUserManager())
+ .WithUsernameAndEmail("author")
+ .Create();
+ await GenerateCheep(_testAuthor.author);
+ }
+
+ [Test]
+ public async Task SpecificCheepViewExists()
+ {
+ await GenerateCheep(_testAuthor.author, "this is author");
+ await Page.GotoAsync("/");
+ await Page.Locator("#messagelist div").Filter(new() { HasText = "—" }).GetByRole(AriaRole.Link).First
+ .ClickAsync();
+ await Expect(Page.GetByRole(AriaRole.Link, new() { Name = "author" })).ToBeVisibleAsync();
+ }
+
+ [Test]
+ public async Task CommentCountUpdates()
+ {
+ await GenerateCheep(_testAuthor.author, "this is author");
+ await RazorPageUtils.Login(_testAuthor);
+ await Page.GotoAsync("/");
+ await Expect(Page.Locator("li").Filter(new() { HasText = "author" }).Locator("small").Nth(1))
+ .ToHaveTextAsync("0");
+ await Page.Locator("#messagelist div").Filter(new() { HasText = "—" }).GetByRole(AriaRole.Link).First
+ .ClickAsync();
+ await Page.GetByPlaceholder("Write a comment").ClickAsync();
+ await Page.GetByPlaceholder("Write a comment").FillAsync("This is a comment");
+ await Page.GetByRole(AriaRole.Button, new() { Name = "Post" }).ClickAsync();
+ await Page.GetByText("This is a comment").ClickAsync();
+ await Expect(Page.GetByText("1", new() { Exact = true })).ToHaveTextAsync("1");
+ }
+
+ [Test]
+ public async Task TestAddComment()
+ {
+ await GenerateCheep(_testAuthor.author, "this is author");
+ await RazorPageUtils.Login(_testAuthor);
+ await Page.GotoAsync("/");
+ await Page.Locator("#messagelist div").Filter(new() { HasText = "—" }).GetByRole(AriaRole.Link).First
+ .ClickAsync();
+ await Page.GetByPlaceholder("Write a comment").ClickAsync();
+ await Page.GetByPlaceholder("Write a comment").FillAsync("This is a comment");
+ await Page.GetByRole(AriaRole.Button, new() { Name = "Post" }).ClickAsync();
+ await Expect(Page.GetByText("This is a comment")).ToBeVisibleAsync();
+ }
+
+ [Test]
+ public async Task CannotPostCommentsWhenNotLoggedIn()
+ {
+ await GenerateCheep(_testAuthor.author, "this is author");
+ await Page.GotoAsync("/");
+ await Page.Locator("#messagelist div").Filter(new() { HasText = "—" }).GetByRole(AriaRole.Link).First
+ .ClickAsync();
+ await Expect(Page.GetByRole(AriaRole.Heading, new() { Name = "Log in to comment on this" }))
+ .ToBeVisibleAsync();
+ }
+}
\ No newline at end of file
diff --git a/Chirp/test/PlaywrightTests/Utils/PageTests/PageTestWithRazorPlaywrightWebApplicationFactory.cs b/Chirp/test/PlaywrightTests/Utils/PageTests/PageTestWithRazorPlaywrightWebApplicationFactory.cs
index 147511c8..daa267cc 100644
--- a/Chirp/test/PlaywrightTests/Utils/PageTests/PageTestWithRazorPlaywrightWebApplicationFactory.cs
+++ b/Chirp/test/PlaywrightTests/Utils/PageTests/PageTestWithRazorPlaywrightWebApplicationFactory.cs
@@ -30,6 +30,7 @@ public override BrowserNewContextOptions ContextOptions()
public void RazorSetup()
{
_razorClient = RazorFactory.CreateClient();
+ // Note this does not make a new database it just wipes the data
RazorFactory.ResetDB();
RazorPageUtils = new PageUtils(Page);
}
diff --git a/Chirp/test/RepositoryTests/CheepRepositoryUnitTest.cs b/Chirp/test/RepositoryTests/CheepRepositoryUnitTest.cs
index fa75125c..573f7cd5 100644
--- a/Chirp/test/RepositoryTests/CheepRepositoryUnitTest.cs
+++ b/Chirp/test/RepositoryTests/CheepRepositoryUnitTest.cs
@@ -2,8 +2,6 @@
using Chirp.Core.DTO;
using Chirp.Infrastructure;
using Chirp.Infrastructure.Model;
-using Microsoft.Data.Sqlite;
-using Microsoft.EntityFrameworkCore;
using RepositoryTests.Utils;
using TestUtilities;
@@ -14,7 +12,7 @@ public class CheepRepositoryUnitTest
{
protected ChirpDBContext Context { get; }
protected ICheepRepository CheepRepository { get; }
-
+
public CheepRepositoryUnitTest(InMemoryDBFixture fixture)
{
fixture.ResetDatabase();
@@ -60,7 +58,7 @@ public async Task GetCheepsByPage_NegativePageSize_ReturnsNull()
//arrange
var author = TestUtils.CreateTestAuthor("Mr. test");
var cheep = new Cheep { Author = author, Message = "test", TimeStamp = DateTime.Now };
-
+
Context.Cheeps.Add(cheep);
await Context.SaveChangesAsync();
@@ -124,7 +122,7 @@ public async Task GetCheepsByPage_ReturnsCorrectSingleCheep()
var author = TestUtils.CreateTestAuthor("Mr. test");
var timeStamp = new DateTime(2000, 01, 01);
var cheep = new Cheep { Author = author, Message = "test", TimeStamp = timeStamp };
-
+
Context.Cheeps.Add(cheep);
await Context.SaveChangesAsync();
@@ -135,7 +133,8 @@ public async Task GetCheepsByPage_ReturnsCorrectSingleCheep()
Assert.NotNull(result);
var resultList = result.ToList();
- var expected = new CheepDTO(author.UserName!, cheep.Message, ((DateTimeOffset)timeStamp).ToUnixTimeSeconds());
+ var expected = new CheepDTO(null, author.UserName!, cheep.Message,
+ ((DateTimeOffset)timeStamp).ToUnixTimeSeconds());
Assert.Single(resultList);
var singleCheep = resultList.ElementAt(0);
@@ -159,7 +158,7 @@ public async Task GetCheepsFromAuthorByPage_ReturnsCorrectCheepWhenMultipleAutho
{
var author = TestUtils.CreateTestAuthor($"name{i}");
authors.Add(author);
-
+
var cheep = new Cheep { Author = author, Message = $"test{i}", TimeStamp = DateTime.Now };
cheeps.Add(cheep);
}
@@ -167,7 +166,7 @@ public async Task GetCheepsFromAuthorByPage_ReturnsCorrectCheepWhenMultipleAutho
Context.Authors.AddRange(authors);
Context.Cheeps.AddRange(cheeps);
await Context.SaveChangesAsync();
-
+
for (int i = 0; i < 5; i++)
{
//act
@@ -188,13 +187,13 @@ public async Task GetCheepsFromAuthor_ReturnsAllCheeps()
List cheeps = [];
for (int i = 0; i < 5; i++)
{
- cheeps.Add(new Cheep{ Author = author, Message = "test" + i, TimeStamp = DateTime.Now });
+ cheeps.Add(new Cheep { Author = author, Message = "test" + i, TimeStamp = DateTime.Now });
}
Context.Authors.Add(author);
Context.Cheeps.AddRange(cheeps);
await Context.SaveChangesAsync();
-
+
var result = (await CheepRepository.GetCheepsFromAuthor(author.UserName!)).ToList();
cheeps.Reverse();
Assert.Equal(5, result.Count);
@@ -202,8 +201,6 @@ public async Task GetCheepsFromAuthor_ReturnsAllCheeps()
{
Assert.Equal(cheeps.ElementAt(i).Message, result.ElementAt(i).Message);
}
-
-
}
[Fact]
@@ -217,7 +214,7 @@ public async Task GetCheepsFromAuthorByPage_ReturnsNoCheepsForNonexistentAuthor(
{
var author = TestUtils.CreateTestAuthor($"name{i}");
authors.Add(author);
-
+
var cheep = new Cheep { Author = author, Message = $"test{i}", TimeStamp = DateTime.Now };
cheeps.Add(cheep);
}
@@ -239,7 +236,7 @@ public async Task GetCheepsFromAuthorByPage_ReturnsAllCheepsFromAuthorForLargeNu
//arrange
var authorA = TestUtils.CreateTestAuthor("Mr. test");
var authorB = TestUtils.CreateTestAuthor("Mr. fake");
-
+
var authTotal = 0;
for (int i = 0; i < 100; i++)
@@ -287,7 +284,7 @@ public async Task CreateCheep()
await Context.SaveChangesAsync();
var unixTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
- var newCheep = new CheepDTO(author.UserName!, "message", unixTimestamp);
+ var newCheep = new CheepDTO(null, author.UserName!, "message", unixTimestamp);
//act
var result = await CheepRepository.CreateCheep(newCheep);
@@ -308,11 +305,11 @@ public async Task GetCheepsFromAuthorsByPage_ReturnsAllCheepsFromAuthorsFromMult
var author2 = TestUtils.CreateTestAuthor("Mr. Test2");
List authors = new();
-
-
+
+
authors.Add(author1.UserName!);
authors.Add(author2.UserName!);
-
+
for (var i = 0; i < 100; i++)
{
if (i % 2 == 0)
@@ -326,12 +323,12 @@ public async Task GetCheepsFromAuthorsByPage_ReturnsAllCheepsFromAuthorsFromMult
Context.Cheeps.Add(cheep);
}
}
-
+
await Context.SaveChangesAsync();
-
+
int pageNo = 1;
int totalCount = 0;
-
+
//Act
while (true)
{
@@ -342,7 +339,7 @@ public async Task GetCheepsFromAuthorsByPage_ReturnsAllCheepsFromAuthorsFromMult
totalCount += count;
pageNo++;
}
-
+
//Assert
Assert.Equal(100, totalCount);
}
@@ -367,12 +364,12 @@ public async Task GetCheepsFromAuthorsByPage_ReturnsNoCheepsFromAuthorWithNoChee
cheeps.Add(cheep1);
cheeps.Add(cheep2);
}
-
+
await Context.SaveChangesAsync();
-
+
//Act
var result = await CheepRepository.GetCheepsFromAuthorsByPage(authors, 1, 20);
-
+
//Assert
Assert.Empty(result.ToList());
}
@@ -388,22 +385,124 @@ public async Task GetCheepsFromAuthorsByPage_ReturnsInChronologicalOrder()
for (var i = 0; i < 5; i++)
{
- var cheep = new Cheep { Author = author1, Message = $"{i}", TimeStamp = DateTimeOffset.FromUnixTimeSeconds(i * 60).DateTime };
+ var cheep = new Cheep
+ { Author = author1, Message = $"{i}", TimeStamp = DateTimeOffset.FromUnixTimeSeconds(i * 60).DateTime };
Context.Cheeps.Add(cheep);
}
-
+
await Context.SaveChangesAsync();
-
+
//Act
var result = await CheepRepository.GetCheepsFromAuthorsByPage(authors, 1, 20);
var resultArray = result.ToArray();
//Assert
Assert.True(resultArray[0].UnixTimestamp >= resultArray[1].UnixTimestamp);
-
+
for (var i = 1; i < resultArray.ToList().Count; i++)
{
- Assert.True(resultArray[i-1].UnixTimestamp >= resultArray[i].UnixTimestamp);
+ Assert.True(resultArray[i - 1].UnixTimestamp >= resultArray[i].UnixTimestamp);
}
}
+
+ #region comments
+
+ [Fact]
+ public async Task GetCheepById_ReturnsCorrectCheep()
+ {
+ // Arrange TEST nr 100 lez gooo
+ var author = TestUtils.CreateTestAuthor("mr. test");
+ var cheep = new Cheep
+ {
+ Id = 0,
+ Message = "test",
+ TimeStamp = DateTime.Now,
+ Author = author
+ };
+
+ Context.Authors.Add(author);
+ Context.Cheeps.Add(cheep);
+ await Context.SaveChangesAsync();
+
+ var result = await CheepRepository.GetCheepById(cheep.Id);
+ Assert.NotNull(result);
+ Assert.Equal(cheep.Id, result.Id);
+ Assert.Equal(cheep.Message, result.Message);
+ }
+
+ [Fact]
+ public async Task GetCommentAmountOnCheep_ReturnsCorrectAmount()
+ {
+ var author = TestUtils.CreateTestAuthor("mr. test");
+ var author2 = TestUtils.CreateTestAuthor("mr. comment");
+ var cheep = new Cheep
+ {
+ Id = 0,
+ Message = "test",
+ TimeStamp = DateTime.Now,
+ Author = author
+ };
+ Context.Authors.Add(author);
+ Context.Authors.Add(author2);
+ Context.Cheeps.Add(cheep);
+ await Context.SaveChangesAsync();
+
+ var comment1 = new CommentDTO(author2.UserName!, cheep.Id, "test comment", 1234);
+ await CheepRepository.AddCommentToCheep(comment1);
+ var count = await CheepRepository.GetCommentAmountOnCheep(cheep.Id);
+ Assert.Equal(1, count);
+
+ comment1 = new CommentDTO(author2.UserName!, cheep.Id, "test comment", 1234);
+ await CheepRepository.AddCommentToCheep(comment1);
+ count = await CheepRepository.GetCommentAmountOnCheep(cheep.Id);
+ Assert.Equal(2, count);
+ }
+
+ [Fact]
+ public async Task AddCommentToCheep_AddsCommentToCheep()
+ {
+ var author = TestUtils.CreateTestAuthor("mr. test");
+ var author2 = TestUtils.CreateTestAuthor("mr. comment");
+ var cheep = new Cheep
+ {
+ Id = 0,
+ Message = "test",
+ TimeStamp = DateTime.Now,
+ Author = author
+ };
+ Context.Authors.Add(author);
+ Context.Authors.Add(author2);
+ Context.Cheeps.Add(cheep);
+ await Context.SaveChangesAsync();
+ var comment1 = new CommentDTO(author2.UserName!, cheep.Id, "test comment", 1234);
+ await CheepRepository.AddCommentToCheep(comment1);
+ Assert.True(Context.Comments.Count() == 1);
+ }
+
+ [Fact]
+ public async Task GetCommentsFromCheep_ReturnsCorrectComments()
+ {
+ var author = TestUtils.CreateTestAuthor("mr. test");
+ var author2 = TestUtils.CreateTestAuthor("mr. comment");
+ var cheep = new Cheep
+ {
+ Id = 0,
+ Message = "test",
+ TimeStamp = DateTime.Now,
+ Author = author
+ };
+ Context.Authors.Add(author);
+ Context.Authors.Add(author2);
+ Context.Cheeps.Add(cheep);
+ await Context.SaveChangesAsync();
+ var comment1 = new CommentDTO(author2.UserName!, cheep.Id, "test comment", 1234);
+ await CheepRepository.AddCommentToCheep(comment1);
+ var comments = await CheepRepository.GetCommentsForCheep(cheep.Id);
+ var first = comments.First();
+ Assert.Single(comments);
+ Assert.Equal(comment1.Message, first.Message);
+ Assert.Equal(comment1.Author, first.Author);
+ }
+
+ #endregion
}
\ No newline at end of file
diff --git a/Chirp/test/Web.UnitTests/PageModelUnitTests.cs b/Chirp/test/Web.UnitTests/PageModelUnitTests.cs
index fe0c31a2..9c75c1dc 100644
--- a/Chirp/test/Web.UnitTests/PageModelUnitTests.cs
+++ b/Chirp/test/Web.UnitTests/PageModelUnitTests.cs
@@ -17,7 +17,9 @@ public void PublicTimeLine()
for (int i = 0; i < 10; i++)
{
cheeps.Add(
- new CheepDTO("mr. test",
+ new CheepDTO(
+ null,
+ "mr. test",
"test",
new DateTimeOffset(DateTime.Now.AddHours(i)).ToUnixTimeSeconds()));
}
diff --git a/Chirp/test/Web.UnitTests/RazorPageIntegrationTest.cs b/Chirp/test/Web.UnitTests/RazorPageIntegrationTest.cs
index 51e0898d..a0af89b5 100644
--- a/Chirp/test/Web.UnitTests/RazorPageIntegrationTest.cs
+++ b/Chirp/test/Web.UnitTests/RazorPageIntegrationTest.cs
@@ -1,9 +1,7 @@
-using System.Globalization;
-using Chirp.Core;
+using Chirp.Core;
using Chirp.Core.DTO;
using Moq;
using Web.UnitTests.Utils;
-using Program = Chirp.Web.Program;
namespace Web.UnitTests;
@@ -19,7 +17,9 @@ public async void DisplayCheeps_On_PublicTimeline()
for (int i = 0; i < 10; i++)
{
cheeps.Add(
- new CheepDTO("mr. " + i + "test",
+ new CheepDTO(
+ null,
+ "mr. " + i + "test",
"test: " + i + "test!",
new DateTimeOffset(DateTime.Now.AddHours(i)).ToUnixTimeSeconds()));
}