Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
001fed2
Update dotnet.yml
hadeer-r Jul 28, 2025
2564ac2
Merge pull request #7 from Askfm-clone/hadeer-r-patch-1
hadeer-r Jul 28, 2025
b48fc7f
Merge pull request #8 from Askfm-clone/Develop
ziad-ashraf7 Jul 29, 2025
40c3c7e
Add:
ziad-ashraf7 Aug 24, 2025
d8cfb1b
FIX: Fixing a userId bug on the Delete Like endpoint
ziad-ashraf7 Aug 25, 2025
c57f1a2
FIX: handling null user exception on AddLikeAsync function
ziad-ashraf7 Aug 25, 2025
f2b42a1
FIX: Added await and remove .Result from DeletLikeAsync function
ziad-ashraf7 Aug 25, 2025
c30d997
test: Added the Async function calls tests to AddLike_WhenCommentNotF…
ziad-ashraf7 Aug 25, 2025
59b594b
test: added await & async to DeleteLike test case
ziad-ashraf7 Aug 25, 2025
1a2aa44
test: Added Task return type for all test cases
ziad-ashraf7 Aug 25, 2025
19ef650
refactor: Added converted return type Task to Task<bool> to DeleteLik…
ziad-ashraf7 Aug 25, 2025
0ef96ce
merge: merged Authintecation PR to the Comments branch
ziad-ashraf7 Aug 25, 2025
eae0ee6
Merge branch 'Askfm-clone:main' into Comments
ziad-ashraf7 Aug 25, 2025
90943a9
refactor: added dependancies injection of ICommentService and ICommen…
ziad-ashraf7 Aug 25, 2025
e814936
Add Default Values to CreatedAt, UpdatedAt and DeletedAt
Mahmoud-Ayman-Saleh Aug 25, 2025
e8d5cea
refactor: edited the current user implementation on UserService and t…
ziad-ashraf7 Aug 27, 2025
81d4655
enabled swaggerUI
ziad-ashraf7 Aug 27, 2025
de36d6f
Refactor:
ziad-ashraf7 Aug 28, 2025
fcce8fd
refactor:
ziad-ashraf7 Aug 28, 2025
462e88c
test: Fixed some fail tests
ziad-ashraf7 Aug 28, 2025
89a363d
Add: Added transactions on the CommentLikeService
ziad-ashraf7 Aug 30, 2025
bca8625
test: Fixed tests fails because of transactions
ziad-ashraf7 Aug 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AskFm/AskFm.API/AskFm.API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AutoMapper" Version="15.0.1" />
<PackageReference Include="DotNetEnv" Version="3.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
Expand Down
8 changes: 4 additions & 4 deletions AskFm/AskFm.API/Controllers/AuthController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public async Task<IActionResult> RegisterUser(RegisterUserDTO registerUser)
ServiceResult<AuthResponseDTO> result = await _authService.RegisterAsync(registerUser);
if (!result.success)
{
return BadRequest(result);
return BadRequest(result.Errors);
}
setRefreshToken(result.Data.RefreshToken.Token,result.Data.RefreshToken.ExpireOn);
return Ok(result);
Expand All @@ -47,7 +47,7 @@ public async Task<IActionResult> Login(LoginDTO login)
ServiceResult<AuthResponseDTO> result = await _authService.LoginAsync(login);
if (!result.success)
{
return BadRequest(result);
return BadRequest(result.Errors);
}
if (!string.IsNullOrEmpty(result.Data.Token))
{
Expand All @@ -70,7 +70,7 @@ public async Task<IActionResult> RefreshToken(int id)

if (!result.success)
{
return BadRequest(result);
return BadRequest(result.Errors);
}

return Ok(result);
Expand All @@ -91,7 +91,7 @@ public async Task<IActionResult> Logout(int id)

if (!result.success)
{
return BadRequest(result);
return BadRequest(result.Errors);
}


Expand Down
161 changes: 161 additions & 0 deletions AskFm/AskFm.API/Controllers/CommentController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
using AskFm.BLL.DTO;
using AskFm.BLL.Services;
using AskFm.BLL.Services.UserIdentityService;
using AskFm.DAL.Interfaces;
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace AskFm.API.Controllers;

[ApiController]
[Route("api/[controller]")]
[Authorize(AuthenticationSchemes = "Bearer")]
public class CommentController : ControllerBase
{

private readonly ICommentLikeService _commentLikeService;
private readonly ICommentService _commentService;
private readonly ILogger<CommentController> _logger;
private readonly IUserService _userService;


public CommentController(
ICommentLikeService commentLikeService,
ICommentService commentService,
IUserService userService,
ILogger<CommentController> logger)
{
_commentLikeService = commentLikeService;
_logger = logger;
_commentService = commentService;
_userService = userService;
}





// GET api/comment/{id}/likes -> get all the likes for a Comment with id = id
[HttpGet("{id}/likes")]
public async Task<IActionResult> GetAllLikes(int id)
{
try
{
var likes = await _commentLikeService.GetLikesForCommentAsync(id);
if (!likes.success)
{
return BadRequest(likes.Errors);
}
return Ok(likes);
}
catch (ArgumentException ex)
{
_logger.LogWarning(ex, "Comment not found with id: {CommentId}", id);
return NotFound(new
{
message = ex.Message,

});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error retrieving likes for comment id: {CommentId}", id);
return StatusCode(500, new { message = "An error occurred while retrieving likes" });
}
}




// POST api/comment/{id}/likes -> add a like for a Comment with id = id
[HttpPost("{id}/likes")]
public async Task<IActionResult> AddLike(int id)
{
try
{
var user = await _userService.GetCurrentUserAsync();

if (!user.success)
{
return BadRequest(user.Errors);
}

Comment on lines +77 to +83
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Map "no current user" to 401, not 400

If the current user cannot be resolved, respond with Unauthorized.

-            if (!user.success)
-            {
-                return BadRequest(user.Errors);
-            }
+            if (!user.success)
+                return Unauthorized(new { errors = user.Errors });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var user = await _userService.GetCurrentUserAsync();
if (!user.success)
{
return BadRequest(user.Errors);
}
var user = await _userService.GetCurrentUserAsync();
if (!user.success)
return Unauthorized(new { errors = user.Errors });
🤖 Prompt for AI Agents
In AskFm/AskFm.API/Controllers/CommentController.cs around lines 77 to 83, the
code returns BadRequest when the current user cannot be resolved; change this to
return an Unauthorized (401) response instead. Replace the
BadRequest(user.Errors) return with Unauthorized(user.Errors) (or at minimum
Unauthorized()) so the API maps "no current user" to HTTP 401.

var createdLike = await _commentLikeService.AddLikeAsync(id, user.Data.Id);

if (!createdLike.success)
{
return BadRequest(createdLike.Errors);
}
return CreatedAtAction(
nameof(GetAllLikes),
new { id = id },
createdLike.Data);
}
catch (ArgumentException ex)
{
_logger.LogWarning(ex, "Invalid request to add like to comment id: {CommentId}", id);
return BadRequest(new { message = ex.Message });
}
catch (InvalidOperationException ex)
{
_logger.LogWarning(ex, "Cannot add like to comment id: {CommentId}", id);
return Conflict(new { message = ex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error adding like to comment id: {CommentId}", id);
return StatusCode(500, new { message = "An error occurred while adding like" });
}
}




[HttpDelete("{id}/likes")]
public async Task<IActionResult> DeleteLike(int id)
{
int userId = 0;
try
{

var user = await _userService.GetCurrentUserAsync();

if (user==null || !user.success)
return BadRequest(user.Errors);

Comment on lines +124 to +126
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

NRE risk when user is null; also wrong status code

You dereference user.Errors even when user == null. Return 401 for null user, and 400 only for a valid result that failed validation.

-            if (user==null || !user.success)
-                return BadRequest(user.Errors);
+            if (user is null)
+                return Unauthorized();
+            if (!user.success)
+                return BadRequest(new { errors = user.Errors });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (user==null || !user.success)
return BadRequest(user.Errors);
if (user is null)
return Unauthorized();
if (!user.success)
return BadRequest(new { errors = user.Errors });
🤖 Prompt for AI Agents
In AskFm/AskFm.API/Controllers/CommentController.cs around lines 124-126, the
code dereferences user.Errors even when user is null and uses the wrong status
code; change the logic to first check if user == null and return Unauthorized()
(401) in that case, then only if user is non-null and user.success is false
return BadRequest(user.Errors); ensure the null check occurs before accessing
user.Errors so no NRE can occur.


userId = user.Data.Id;
var comment = await _commentService.GetCommentAsync(id);

if (comment == null || !user.success)
return BadRequest(user.Errors);

Comment on lines +131 to +133
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Incorrect condition; comment null should yield 404, not reuse user.success

You re-check user.success and return user.Errors. Return NotFound for a missing comment.

-            if (comment == null || !user.success)
-                return BadRequest(user.Errors);
+            if (comment is null)
+                return NotFound(new { message = $"Comment with id {id} not found" });

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In AskFm/AskFm.API/Controllers/CommentController.cs around lines 131 to 133, the
current combined condition incorrectly treats a null comment as a BadRequest
tied to user.success; split the checks so that if comment == null you return
NotFound(), and separately if !user.success you return BadRequest(user.Errors);
update the control flow accordingly to ensure the correct response codes are
returned for each case.


var result = await _commentLikeService.DeleteLikeAsync(id, userId);

if (!result.success)
{
return BadRequest(result.Errors);
}

return NoContent();
}
catch (ArgumentException ex)
{
_logger.LogWarning(ex, "Like not found for comment id: {CommentId} and user {UserId}", id, userId);
return NotFound(new { message = ex.Message });
}
catch (UnauthorizedAccessException ex)
{
_logger.LogWarning(ex, "Unauthorized delete attempt for comment id: {CommentId} by user {UserId}", id, userId);
return Forbid();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deleting like from comment id: {CommentId} by user {UserId}", id, userId);
return StatusCode(500, new { message = "An error occurred while deleting like" });
}
}

}
15 changes: 12 additions & 3 deletions AskFm/AskFm.API/Controllers/UserController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,25 @@ public async Task<IActionResult> GetUsers()
}

[HttpGet]
[Route("current-user")]
[Route("profile")]
public async Task<IActionResult> GetCurrentUserAsync()
{
var result = await _userService.GetCurrentUserAsync();

if (!result.success)
{
return BadRequest(result.Errors);
}
return Ok(result.Data);
ReadUserDTO readUserDTO = new ReadUserDTO()
{
Name = result.Data.Name,
Email = result.Data.Email,
AvatarPath = result.Data.AvatarPath,
Bio = result.Data.Bio,
followerCount = result.Data.FollowersCount,
LastSeen = result.Data.LastSeen,
};
return Ok(readUserDTO);
}
/*
GET Users only for now
Expand Down
3 changes: 3 additions & 0 deletions AskFm/AskFm.API/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text;
using AskFm.BLL.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
using AskFm.DAL;
Expand Down Expand Up @@ -44,6 +45,8 @@ public static void Main(string[] args)
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
builder.Services.AddScoped<IAuthService, AuthService>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<ICommentLikeService, CommentLikeService>();
builder.Services.AddScoped<ICommentService, CommentService>();
Comment on lines +48 to +49
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Good DI registrations; ensure AutoMapper (if used) is registered too.

If CommentLikeService/CommentService use IMapper, you need AddAutoMapper(...) in DI.

Search for IMapper usage:

If found, add:

+builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

The scoped registrations for the services look correct alongside IUnitOfWork.


🏁 Script executed:

#!/bin/bash
rg -nP --type cs -C2 '\bIMapper\b|AutoMapper'

Length of output: 1565


Ensure AutoMapper is registered and injected for all IMapper usages

We’ve confirmed IMapper is used in both the BLL service and the API controller but neither the DI registration nor the constructors reflect that:

  • AskFm/AskFm.BLL/Services/CommentLikeService.cs declares
    private readonly IMapper _mapper;
    public CommentLikeService(IUnitOfWork unitOfWork, ILogger<CommentLikeService> logger) {}
    → Missing constructor parameter and assignment for IMapper _mapper.
  • AskFm/AskFm.API/Controllers/CommentController.cs includes
    using AutoMapper;
    public class CommentController : ControllerBase
    {
        private readonly IMapper _mapper;}
    → Likewise needs IMapper in its constructor.

Please apply the following updates:

• In Program.cs (AskFm/AskFm.API/Program.cs), before building the host, register AutoMapper:

 builder.Services.AddScoped<ICommentLikeService, CommentLikeService>();
 builder.Services.AddScoped<ICommentService, CommentService>();
+builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

• Update CommentLikeService constructor:

- public CommentLikeService(IUnitOfWork unitOfWork, ILogger<CommentLikeService> logger)
+ public CommentLikeService(IUnitOfWork unitOfWork,
+                           ILogger<CommentLikeService> logger,
+                           IMapper mapper)
 {
     _unitOfWork = unitOfWork;
     _logger = logger;
+    _mapper = mapper;
 }

• Update CommentController constructor similarly to accept and assign IMapper.

These changes will ensure AutoMapper is available in DI and that each consuming class receives its IMapper instance.

🤖 Prompt for AI Agents
In AskFm/AskFm.API/Program.cs around lines 40-41, AutoMapper is not registered
in DI while IMapper is required by services and controllers; register AutoMapper
(e.g., builder.Services.AddAutoMapper(typeof(Startup) or an assembly marker)
before building the host. In AskFm/AskFm.BLL/Services/CommentLikeService.cs add
an IMapper parameter to the constructor and assign it to the existing private
readonly IMapper _mapper field (and import AutoMapper namespace), preserving
existing parameters and logger. In
AskFm/AskFm.API/Controllers/CommentController.cs update the controller
constructor to accept IMapper and assign it to the controller's private readonly
IMapper _mapper field (add using AutoMapper if missing). Ensure constructor
parameter order and DI registrations remain consistent so IMapper is injected
where used.

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
Expand Down
6 changes: 4 additions & 2 deletions AskFm/AskFm.API/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5180",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
Expand All @@ -13,7 +14,8 @@
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7115;http://localhost:5180",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
Expand Down
1 change: 1 addition & 0 deletions AskFm/AskFm.BLL/AskFm.BLL.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="AutoMapper" Version="15.0.1" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Confirm AutoMapper DI integration point; add DI package in the API if needed.

Adding AutoMapper here is fine if BLL uses IMapper internally. If the mapping is configured/registered in the API via services.AddAutoMapper(...), ensure the API project references AutoMapper.Extensions.Microsoft.DependencyInjection; that DI package is typically added to the startup project rather than BLL.

Run to verify usage and registration points:


🏁 Script executed:

#!/bin/bash
# Find AutoMapper registrations and usages
rg -n --type=cs '\bAddAutoMapper\b|IMapper\b|MapperConfiguration\b|Profile\b' -C3

Length of output: 702


🏁 Script executed:

#!/bin/bash
# Search for DI package reference in all csproj files
rg -n "AutoMapper.Extensions.Microsoft.DependencyInjection" -g "*.csproj"
# Search for DI registration code (services.AddAutoMapper) in C# files
rg -n --type=cs "AddAutoMapper" -C3

Length of output: 109


Add AutoMapper DI registration in the API project

The BLL layer is injecting IMapper (e.g., in CommentLikeService), but I didn’t find any DI package reference or services.AddAutoMapper(...) calls across the solution. Without registering AutoMapper in your startup project, those injections will fail at runtime.

Please address the following:

  • In your API project’s .csproj (e.g. AskFm/AskFm.API/AskFm.API.csproj), add:
    <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="[latest-compatible-version]" />
  • In your startup configuration (Program.cs or Startup.cs), register AutoMapper, for example:
    services.AddAutoMapper(typeof(Startup).Assembly);
    // or specify assemblies containing your Profile classes:
    // services.AddAutoMapper(typeof(CommentMappingProfile).Assembly);

This will ensure that IMapper is properly configured and available to the BLL.

🤖 Prompt for AI Agents
In AskFm/AskFm.BLL/AskFm.BLL.csproj around line 15, the project references
AutoMapper but the API project lacks the DI integration; add the
AutoMapper.Extensions.Microsoft.DependencyInjection package reference to your
API project's .csproj (AskFm/AskFm.API/AskFm.API.csproj) and then register
AutoMapper during startup (in Program.cs or Startup.cs) by calling the
AddAutoMapper extension and pointing it to the assembly or assemblies that
contain your AutoMapper Profile classes (for example the assembly containing
your mapping profiles or Startup class) so that IMapper can be resolved by the
BLL at runtime.

<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.0" />
</ItemGroup>

Expand Down
9 changes: 9 additions & 0 deletions AskFm/AskFm.BLL/DTO/CommentLikeDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace AskFm.BLL.DTO;

public class CommentLikeDto
{
public int CommentId { get; set; }
public int UserId { get; set; }
public string UserName { get; set; }
public DateTime CreatedAt { get; set; }
}
Loading
Loading