Skip to content

Commit be646ed

Browse files
Merge pull request #103 from boolean-uk/89-backend---add-new-endpoints-for-comment-functionality
89 backend add new endpoints for comment functionality
2 parents 9e03ba6 + c5bdd16 commit be646ed

File tree

8 files changed

+756
-183
lines changed

8 files changed

+756
-183
lines changed

exercise.tests/IntegrationTests/PostTests.cs

Lines changed: 498 additions & 146 deletions
Large diffs are not rendered by default.

exercise.tests/IntegrationTests/UserTests.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,6 @@ public async Task Register_success(string username, string email, string passwor
8282
public async Task Register_Failure(string username, string email, string password)
8383
{
8484
var uniqueId = DateTime.UtcNow.ToString("yyMMddHHmmssffff");
85-
string firstName = "Ole";
86-
string lastName = "Petterson";
8785

8886
string uniqueUsername = username.Length > 0 ? username + uniqueId : "";
8987
RegisterRequestDTO body = new RegisterRequestDTO
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using exercise.wwwapi.DTOs.GetUsers;
2+
3+
namespace exercise.wwwapi.DTOs.Posts
4+
{
5+
public class CreatePostCommentDTO
6+
{
7+
//public required int Userid { get; set; }
8+
public required string Content { get; set; }
9+
}
10+
}

exercise.wwwapi/DTOs/Posts/CreatePostDTO.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
{
33
public class CreatePostDTO
44
{
5-
public required int Userid { get; set; }
65
public required string Content { get; set; }
76

87
}

exercise.wwwapi/Data/PostData.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,11 @@ public class PostData
5656
};
5757
DateTime somedate = new DateTime(2020, 12, 05, 0, 0, 0, DateTimeKind.Utc);
5858
private List<Post> _posts = new List<Post>();
59-
private int numusers = 300;
6059
public PostData(List<User> users)
6160
{
6261
Random random = new Random(1);
63-
64-
for (int i = 1; i < 25; i++)
62+
63+
for (int i = 1; i < users.Count / 5; i++)
6564
{
6665
string subject = _subject[random.Next(9 - 1)];
6766
string obj = _objects[random.Next(16 - 1)];

exercise.wwwapi/Endpoints/PostEndpoints.cs

Lines changed: 224 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
using exercise.wwwapi.DTOs;
33
using exercise.wwwapi.DTOs.GetUsers;
44
using exercise.wwwapi.DTOs.Posts;
5+
using exercise.wwwapi.Helpers;
56
using exercise.wwwapi.Models;
67
using exercise.wwwapi.Repository;
8+
using Microsoft.AspNetCore.Authorization;
79
using Microsoft.AspNetCore.Mvc;
810
using Microsoft.EntityFrameworkCore;
11+
using System.Security.Claims;
912

1013
namespace exercise.wwwapi.Endpoints
1114
{
@@ -16,68 +19,101 @@ public static void ConfigurePostEndpoints(this WebApplication app)
1619
var posts = app.MapGroup("posts");
1720
posts.MapPost("/", CreatePost).WithSummary("Create post");
1821
posts.MapGet("/", GetAllPosts).WithSummary("Get all posts");
19-
posts.MapPatch("/{id}", UpdatePost).WithSummary("Update a certain post");
20-
posts.MapDelete("/{id}", DeletePost).WithSummary("Remove a certain post");
22+
posts.MapPatch("/{postid}", UpdatePost).WithSummary("Update a certain post");
23+
posts.MapDelete("/{postid}", DeletePost).WithSummary("Remove a certain post");
24+
25+
posts.MapPost("/{postId}/comments", AddCommentToPost).WithSummary("Add a new comment to a post");
26+
posts.MapGet("/{postId}/comments", GetCommentsForPost).WithSummary("Get comments for a specific post");
27+
28+
// Standalone comment endpoints for editing/deleting
29+
var comments = app.MapGroup("comments");
30+
comments.MapPatch("/{commentId}", UpdateComment).WithSummary("Edit an existing comment");
31+
comments.MapDelete("/{commentId}", DeleteCommentById).WithSummary("Remove an existing comment");
32+
33+
// Endpoints to get by user
34+
posts.MapGet("/user/{userId}", GetPostsByUser).WithSummary("Get posts by a specific user");
35+
comments.MapGet("/user/{userId}", GetCommentsByUser).WithSummary("Get comments by a specific user");
2136
}
2237

38+
[Authorize]
2339
[ProducesResponseType(StatusCodes.Status200OK)]
2440
[ProducesResponseType(StatusCodes.Status404NotFound)]
2541
[ProducesResponseType(StatusCodes.Status400BadRequest)]
26-
public static IResult CreatePost(IRepository<User> userservice, IRepository<Post> postservice, IMapper mapper, CreatePostDTO request)
42+
public static IResult CreatePost(
43+
IRepository<User> userservice,
44+
IRepository<Post> postservice,
45+
IMapper mapper,
46+
ClaimsPrincipal user,
47+
CreatePostDTO request
48+
)
2749
{
28-
29-
User? user = userservice.GetById(request.Userid);
50+
int? userid = user.UserRealId();
51+
User? dbUser = userservice.GetById(userid);
3052
if (user == null)
3153
return Results.NotFound(new ResponseDTO<Object> { Message = "Invalid userID" });
3254

3355
if (string.IsNullOrWhiteSpace(request.Content))
3456
return Results.BadRequest(new ResponseDTO<Object> { Message = "Content cannot be empty" });
3557

36-
Post post = new Post() { CreatedAt = DateTime.UtcNow, NumLikes = 0, UserId = request.Userid, Content = request.Content };
58+
Post post = new Post() { CreatedAt = DateTime.UtcNow, NumLikes = 0, UserId= dbUser.Id, Content=request.Content };
3759

3860
// is a try catch needed here?
3961
postservice.Insert(post);
4062
postservice.Save();
4163

4264

43-
UserBasicDTO userBasicDTO = mapper.Map<UserBasicDTO>(user);
65+
UserBasicDTO userBasicDTO = mapper.Map<UserBasicDTO>(dbUser);
4466
PostDTO postDTO = mapper.Map<PostDTO>(post);
4567
postDTO.User = userBasicDTO;
4668

4769
ResponseDTO<PostDTO> response = new ResponseDTO<PostDTO>
4870
{
49-
Message = "success",
71+
Message = "Success",
5072
Data = postDTO
5173
};
5274

5375
return Results.Created($"/posts/{post.Id}", response);
5476
}
77+
[Authorize]
5578
[ProducesResponseType(StatusCodes.Status200OK)]
5679
public static IResult GetAllPosts(IRepository<Post> service, IMapper mapper)
5780
{
5881
IEnumerable<Post> results = service.GetWithIncludes(q => q.Include(p => p.User).Include(p => p.Comments).ThenInclude(c => c.User));
5982
IEnumerable<PostDTO> postDTOs = mapper.Map<IEnumerable<PostDTO>>(results);
6083
ResponseDTO<IEnumerable<PostDTO>> response = new ResponseDTO<IEnumerable<PostDTO>>()
6184
{
62-
Message = "success",
85+
Message = "Success",
6386
Data = postDTOs
6487
};
6588
return TypedResults.Ok(response);
6689
}
6790

91+
[Authorize]
6892
[ProducesResponseType(StatusCodes.Status200OK)]
6993
[ProducesResponseType(StatusCodes.Status400BadRequest)]
70-
public static IResult UpdatePost(IRepository<Post> service, IMapper mapper, int id, UpdatePostDTO request)
71-
{
72-
if (string.IsNullOrWhiteSpace(request.Content)) return TypedResults.BadRequest(new ResponseDTO<object>
73-
{
74-
Message = "Content cannot be empty"
75-
});
94+
[ProducesResponseType(StatusCodes.Status403Forbidden)]
7695

77-
Post? post = service.GetById(id, q => q.Include(p => p.User));
96+
public static IResult UpdatePost(IRepository<Post> service, IMapper mapper, ClaimsPrincipal user, int postid, UpdatePostDTO request)
97+
{
98+
if (string.IsNullOrWhiteSpace(request.Content)) return TypedResults.BadRequest(new ResponseDTO<object>{
99+
Message = "Content cannot be empty"
100+
});
101+
102+
Post? post = service.GetById(postid, q=>q.Include(p => p.User));
78103

79104
if (post == null) return TypedResults.NotFound(new ResponseDTO<Object> { Message = "Post not found" });
80105

106+
Console.WriteLine($"Role:{user.Role()} {Roles.student}| {post.UserId} {user.UserRealId()}");
107+
if (post.UserId != user.UserRealId() && user.Role() == (int)Roles.student)
108+
{
109+
var forbiddenResponse = new ResponseDTO<object>
110+
{
111+
Message = "You are not authorized to edit this post."
112+
};
113+
return TypedResults.Json(forbiddenResponse, statusCode: StatusCodes.Status403Forbidden);
114+
}
115+
116+
81117
post.Content = request.Content;
82118
post.UpdatedAt = DateTime.UtcNow;
83119

@@ -90,17 +126,186 @@ public static IResult UpdatePost(IRepository<Post> service, IMapper mapper, int
90126
return TypedResults.Ok(new ResponseDTO<PostDTO> { Message = "Success", Data = postDTO });
91127
}
92128

129+
[Authorize]
93130
[ProducesResponseType(StatusCodes.Status200OK)]
94131
[ProducesResponseType(StatusCodes.Status404NotFound)]
95-
private static IResult DeletePost(IRepository<Post> service, int id)
132+
[ProducesResponseType(StatusCodes.Status403Forbidden)]
133+
private static IResult DeletePost(IRepository<Post> service, ClaimsPrincipal user, int postid)
96134
{
97-
Post? post = service.GetById(id, q => q.Include(p => p.User).Include(p => p.Comments).ThenInclude(c => c.User));
135+
Post? post = service.GetById(postid, q => q.Include(p => p.User).Include(p => p.Comments).ThenInclude(c => c.User));
98136
if (post == null) return TypedResults.NotFound(new ResponseDTO<Object> { Message = "Post not found" });
99137

100-
service.Delete(id);
138+
if (user.Role() == (int)Roles.student && post.UserId != user.UserRealId())
139+
{
140+
var forbiddenResponse = new ResponseDTO<object>
141+
{
142+
Message = "You are not authorized to delete this post."
143+
};
144+
return TypedResults.Json(forbiddenResponse, statusCode: StatusCodes.Status403Forbidden);
145+
}
146+
service.Delete(postid);
101147
service.Save();
102148

103149
return TypedResults.Ok(new ResponseDTO<PostDTO> { Message = "Success" });
104150
}
151+
152+
[Authorize]
153+
[ProducesResponseType(StatusCodes.Status201Created)]
154+
[ProducesResponseType(StatusCodes.Status404NotFound)]
155+
[ProducesResponseType(StatusCodes.Status400BadRequest)]
156+
private static IResult AddCommentToPost(
157+
IRepository<PostComment> commentService,
158+
IRepository<Post> postService,
159+
IRepository<User> userService,
160+
IMapper mapper,
161+
ClaimsPrincipal user,
162+
int postId,
163+
CreatePostCommentDTO request)
164+
{
165+
// Check if post exists
166+
var post = postService.GetById(postId);
167+
if (post == null)
168+
{
169+
return TypedResults.NotFound(new ResponseDTO<object> { Message = "Post not found." });
170+
}
171+
172+
// Check if user exists
173+
var dbUser = userService.GetById(user.UserRealId());
174+
if (dbUser == null)
175+
{
176+
return TypedResults.NotFound(new ResponseDTO<object> { Message = "User not found." });
177+
}
178+
179+
// Validate content
180+
if (string.IsNullOrWhiteSpace(request.Content))
181+
{
182+
return TypedResults.BadRequest(new ResponseDTO<object> { Message = "Comment content cannot be empty." });
183+
}
184+
185+
var comment = new PostComment
186+
{
187+
Content = request.Content,
188+
UserId = dbUser.Id,
189+
PostId = postId,
190+
CreatedAt = DateTime.UtcNow
191+
};
192+
193+
commentService.Insert(comment);
194+
commentService.Save();
195+
196+
var createdComment = commentService.GetById(comment.Id, q => q.Include(c => c.User));
197+
var commentDto = mapper.Map<PostCommentDTO>(createdComment);
198+
199+
return TypedResults.Created($"/comments/{comment.Id}", new ResponseDTO<PostCommentDTO> { Message = "Success", Data = commentDto });
200+
}
201+
202+
[Authorize]
203+
[ProducesResponseType(StatusCodes.Status200OK)]
204+
private static IResult GetCommentsForPost(IRepository<Post> postservice, IMapper mapper, int postId)
205+
{
206+
Post? post = postservice.GetById(postId, q => q.Include(p => p.User).Include(p => p.Comments).ThenInclude(c => c.User));
207+
if (post == null) return TypedResults.NotFound(new ResponseDTO<Object> { Message = "Post not found" });
208+
List<PostComment> comments = [.. post.Comments];
209+
List<PostCommentDTO> commentsDTO = mapper.Map<List<PostCommentDTO>>(comments);
210+
return TypedResults.Ok(new ResponseDTO<List<PostCommentDTO>> { Message = "Success", Data = commentsDTO });
211+
}
212+
213+
[ProducesResponseType(StatusCodes.Status200OK)]
214+
[ProducesResponseType(StatusCodes.Status404NotFound)]
215+
[ProducesResponseType(StatusCodes.Status400BadRequest)]
216+
private static IResult UpdateComment(
217+
IRepository<PostComment> service,
218+
IMapper mapper,
219+
ClaimsPrincipal user,
220+
int commentId,
221+
CreatePostCommentDTO request)
222+
{
223+
if (string.IsNullOrWhiteSpace(request.Content))
224+
{
225+
return TypedResults.BadRequest(new ResponseDTO<object> { Message = "Content cannot be empty." });
226+
}
227+
228+
var comment = service.GetById(commentId, q => q.Include(c => c.User));
229+
if (comment == null) return TypedResults.NotFound(new ResponseDTO<object> { Message = "Comment not found." });
230+
231+
//Console.WriteLine($"Role:{user.Role()} {Roles.student.ToString()}| {comment.UserId} {user.UserRealId()}");
232+
if (comment.UserId != user.UserRealId() && user.Role() == (int)Roles.student)
233+
{
234+
var forbiddenResponse = new ResponseDTO<object>
235+
{
236+
Message = "You are not authorized to edit this comment."
237+
};
238+
return TypedResults.Json(forbiddenResponse, statusCode: StatusCodes.Status403Forbidden);
239+
}
240+
241+
comment.Content = request.Content;
242+
comment.UpdatedAt = DateTime.UtcNow;
243+
244+
service.Update(comment);
245+
service.Save();
246+
247+
var commentDto = mapper.Map<PostComment>(comment);
248+
return TypedResults.Ok(new ResponseDTO<PostComment> { Message = "Comment updated successfully.", Data = commentDto });
249+
}
250+
251+
[Authorize]
252+
[ProducesResponseType(StatusCodes.Status200OK)]
253+
[ProducesResponseType(StatusCodes.Status404NotFound)]
254+
private static IResult DeleteCommentById(IRepository<PostComment> service, ClaimsPrincipal user, int commentId)
255+
{
256+
var comment = service.GetById(commentId);
257+
if (comment == null)
258+
{
259+
return TypedResults.NotFound(new ResponseDTO<object> { Message = "Comment not found." });
260+
}
261+
262+
if (user.Role() == (int)Roles.student && comment.UserId != user.UserRealId())
263+
{
264+
var forbiddenResponse = new ResponseDTO<object>
265+
{
266+
Message = "You are not authorized to delete this comment."
267+
};
268+
return TypedResults.Json(forbiddenResponse, statusCode: StatusCodes.Status403Forbidden);
269+
}
270+
271+
service.Delete(commentId);
272+
service.Save();
273+
274+
return TypedResults.Ok(new ResponseDTO<object> { Message = "Comment deleted successfully." });
275+
}
276+
277+
[Authorize]
278+
[ProducesResponseType(StatusCodes.Status200OK)]
279+
[ProducesResponseType(StatusCodes.Status404NotFound)]
280+
private static IResult GetPostsByUser(IRepository<Post> service, IMapper mapper, int userid)
281+
{
282+
IEnumerable<Post> results = service.GetWithIncludes(q => q.Where(p => p.UserId == userid).Include(p => p.User).Include(p => p.Comments).ThenInclude(c => c.User));
283+
if (results.Count() == 0) return TypedResults.NotFound(new ResponseDTO<Object> { Message = "No posts found for this user" });
284+
285+
IEnumerable<PostDTO> postDTOs = mapper.Map<IEnumerable<PostDTO>>(results);
286+
ResponseDTO<IEnumerable<PostDTO>> response = new ResponseDTO<IEnumerable<PostDTO>>()
287+
{
288+
Message = "Success",
289+
Data = postDTOs
290+
};
291+
return TypedResults.Ok(response);
292+
}
293+
294+
[Authorize]
295+
[ProducesResponseType(StatusCodes.Status200OK)]
296+
[ProducesResponseType(StatusCodes.Status404NotFound)]
297+
private static IResult GetCommentsByUser(IRepository<PostComment> service, IMapper mapper, int userid)
298+
{
299+
IEnumerable<PostComment> results = service.GetWithIncludes(q => q.Where(p => p.UserId == userid).Include(p => p.User));
300+
if (results.Count() == 0) return TypedResults.NotFound(new ResponseDTO<Object> { Message = "No comments found for this user" });
301+
302+
IEnumerable<PostCommentDTO> PostCommentDTOs = mapper.Map<IEnumerable<PostCommentDTO>>(results);
303+
ResponseDTO<IEnumerable<PostCommentDTO>> response = new ResponseDTO<IEnumerable<PostCommentDTO>>()
304+
{
305+
Message = "Success",
306+
Data = PostCommentDTOs
307+
};
308+
return TypedResults.Ok(response);
309+
}
105310
}
106311
}

0 commit comments

Comments
 (0)