Skip to content

Commit 383cd8b

Browse files
Refactor user and post-related services, add XML comments, and enhance post functionality.
- Renamed and reorganized namespaces for `Users` and `Posts` services to improve structure. - Deleted and recreated `CurrentUser.cs` under a new namespace for better organization. - Added extensive XML comments and documentation for methods in `Post`, `PostService`, and related services. - Introduced `UpdatePostRequest` DTO for post updates. - Enhanced post creation by separating feed and project post logic in `PostService`. - Updated `PostResponse` DTO to include `CanEdit` and `CanDelete` flags for user-specific features. - Adjusted `IUserRepository`, `IUserService`, and respective implementations to use updated namespaces. - Added functionality in `PostRepository` to support post updates. - Updated DI container in `Program.cs` to reflect changes.
1 parent 6dcccb7 commit 383cd8b

24 files changed

+434
-107
lines changed

Program.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,15 @@
77
using sparkly_server.Services.Auth;
88
using sparkly_server.Services.Auth.provider;
99
using sparkly_server.Services.Auth.service;
10+
using sparkly_server.Services.Posts.repo;
11+
using sparkly_server.Services.Posts.service;
1012
using sparkly_server.Services.Projects;
1113
using sparkly_server.Services.Projects.repo;
1214
using sparkly_server.Services.Projects.service;
1315
using sparkly_server.Services.Users;
16+
using sparkly_server.Services.Users.CurrentUser;
17+
using sparkly_server.Services.Users.repo;
18+
using sparkly_server.Services.Users.service;
1419
using System.Text;
1520

1621
var builder = WebApplication.CreateBuilder(args);
@@ -51,12 +56,18 @@
5156
// Domain / app services
5257
builder.Services.AddScoped<IUserRepository, UserRepository>();
5358
builder.Services.AddScoped<IUserService, UserService>();
59+
5460
builder.Services.AddScoped<IJwtProvider, JwtProvider>();
5561
builder.Services.AddScoped<IAuthService, AuthService>();
62+
5663
builder.Services.AddScoped<ICurrentUser, CurrentUser>();
64+
5765
builder.Services.AddScoped<IProjectRepository, ProjectRepository>();
5866
builder.Services.AddScoped<IProjectService, ProjectService>();
5967

68+
builder.Services.AddScoped<IPostRepository, PostRepository>();
69+
builder.Services.AddScoped<IPostService, PostService>();
70+
6071
// Database
6172
if (builder.Environment.IsEnvironment("Testing"))
6273
{
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
22
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADbFunctionsExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fc1c46ed28c61e1caa79185e4375a8ae7cd11cd5ba8853dcb37577f93f2ca8d5_003FDbFunctionsExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
33
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APrincipalExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fcc1ad596b4fde937674ff6c832655e53b7c6cc97b1b7a38893ad352a788057_003FPrincipalExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
4-
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=64599883_002D44d5_002D4d21_002D921f_002D1ab4ff07b455/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" Name="CreateProject_Should_Create_Project_For_Authenticated_User" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
5-
&lt;TestAncestor&gt;
6-
&lt;TestId&gt;xUnit::E26AB9F3-8A34-4AB8-A503-F0B851823527::net9.0::sparkly_server.test.ProjectTest.CreateProject_Should_Create_Project_For_Authenticated_User&lt;/TestId&gt;
7-
&lt;/TestAncestor&gt;
4+
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=64599883_002D44d5_002D4d21_002D921f_002D1ab4ff07b455/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" Name="CreateProject_Should_Create_Project_For_Authenticated_User" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
5+
&lt;TestAncestor&gt;&#xD;
6+
&lt;TestId&gt;xUnit::E26AB9F3-8A34-4AB8-A503-F0B851823527::net9.0::sparkly_server.test.ProjectTest.CreateProject_Should_Create_Project_For_Authenticated_User&lt;/TestId&gt;&#xD;
7+
&lt;/TestAncestor&gt;&#xD;
88
&lt;/SessionState&gt;</s:String></wpf:ResourceDictionary>

src/Controllers/Posts/PostsController.cs

Lines changed: 107 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@ public class PostsController : ControllerBase
1515
{
1616
private readonly IPostService _posts;
1717

18-
private PostsController(IPostService posts) => _posts = posts;
18+
public PostsController(IPostService posts) => _posts = posts;
1919

20-
// helper to get the current user's id
20+
/// <summary>
21+
/// Retrieves the unique identifier (GUID) of the currently authenticated user.
22+
/// </summary>
23+
/// <returns>
24+
/// A <see cref="Guid"/> representing the user's unique identifier, or <see cref="Guid.Empty"/> if the user is not authenticated.
25+
/// </returns>
2126
private Guid GetUserId()
2227
{
2328
var idValue = User.FindFirstValue(ClaimTypes.NameIdentifier);
@@ -68,47 +73,130 @@ public async Task<ActionResult<IReadOnlyList<Post>>> GetProjectPosts(
6873
}
6974

7075
/// <summary>
71-
/// Retrieves a paginated list of posts for the authenticated user's feed.
76+
/// Retrieves a paginated feed of posts for the current user.
7277
/// </summary>
73-
/// <param name="page">The page number to retrieve, starting from 1.</param>
74-
/// <param name="pageSize">The number of posts per page.</param>
75-
/// <param name="ct">A cancellation token for the operation.</param>
78+
/// <param name="page">The page number of the feed to retrieve. Defaults to 1.</param>
79+
/// <param name="pageSize">The number of posts to include per page. Defaults to 20.</param>
80+
/// <param name="ct">A cancellation token to observe while awaiting the task.</param>
7681
/// <returns>
77-
/// An <see cref="ActionResult{T}"/> containing a read-only list of posts in the user's feed.
82+
/// An <see cref="ActionResult{T}"/> containing a list of <see cref="PostResponse"/> objects representing the user's feed.
7883
/// </returns>
7984
[HttpGet("feed")]
80-
public async Task<ActionResult<IReadOnlyList<Post>>> GetFeed(
85+
public async Task<ActionResult<IReadOnlyList<PostResponse>>> GetFeed(
8186
[FromQuery] int page = 1,
8287
[FromQuery] int pageSize = 20,
83-
[FromQuery] CancellationToken ct = default)
88+
CancellationToken ct = default)
8489
{
8590
var userId = GetUserId();
91+
Console.WriteLine($"FEED userId = {userId}");
92+
8693
var posts = await _posts.GetFeedPostAsync(userId, page, pageSize, ct);
87-
return Ok(posts);
94+
95+
var response = posts
96+
.Select(p => p.ToResponse(userId))
97+
.ToList();
98+
99+
return Ok(response);
88100
}
89101

102+
90103
/// <summary>
91-
/// Creates a new post associated with a specific project and authored by the current user.
104+
/// Creates a new post associated with a specific project.
92105
/// </summary>
93-
/// <param name="request">The details of the post to be created, including project ID, title, and content.</param>
106+
/// <param name="request">The details of the post to be created, including title and content.</param>
107+
/// <param name="projectId">The unique identifier (GUID) of the project the post belongs to.</param>
94108
/// <returns>
95-
/// An <see cref="ActionResult{T}"/> containing the created post's response details,
96-
/// or a BadRequest result if the creation fails.
109+
/// An <see cref="ActionResult{T}"/> containing the created post response including its properties.
97110
/// </returns>
98-
[HttpPost]
99-
public async Task<ActionResult<PostResponse>> Create([FromBody] CreatePostRequest request)
111+
[HttpPost("create/project/{projectId:guid}")]
112+
public async Task<ActionResult<PostResponse>> Create([FromBody] CreatePostRequest request, [FromRoute] Guid projectId)
100113
{
101114
Guid userId = GetUserId();
102115

103-
var post = await _posts.AddPostAsync(
104-
request.ProjectId,
116+
var post = await _posts.AddProjectPostAsync(
105117
userId,
118+
projectId,
106119
request.Title,
107120
request.Content);
108121

109-
var response = post.ToResponse();
122+
var response = post.ToResponse(userId);
110123

111124
return Created(string.Empty, response);
112125
}
126+
127+
/// <summary>
128+
/// Creates a new post for the feed with the specified content and title.
129+
/// </summary>
130+
/// <param name="request">The details of the post to create, including title and content.</param>
131+
/// <param name="ct">The cancellation token to monitor for request cancellation.</param>
132+
/// <returns>
133+
/// An <see cref="ActionResult{T}"/> containing the created feed post details.
134+
/// </returns>
135+
[HttpPost("create/feed")]
136+
public async Task<ActionResult<PostResponse>> CreateFeedPost(
137+
[FromBody] CreatePostRequest request,
138+
CancellationToken ct)
139+
{
140+
var userId = GetUserId();
141+
142+
var post = await _posts.AddFeedPostAsync(
143+
userId,
144+
request.Title,
145+
request.Content,
146+
ct);
147+
148+
var response = post.ToResponse(userId);
149+
150+
return Created(string.Empty, response);
151+
}
152+
153+
/// <summary>
154+
/// Updates an existing post with new information provided by the user.
155+
/// </summary>
156+
/// <param name="postId">The unique identifier (GUID) of the post to be updated.</param>
157+
/// <param name="request">An <see cref="UpdatePostRequest"/> containing the new title and content for the post.</param>
158+
/// <param name="ct">A <see cref="CancellationToken"/> to observe while waiting for the task to complete.</param>
159+
/// <returns>
160+
/// An <see cref="IActionResult"/> indicating the result of the update operation.
161+
/// Returns a NotFound result if the post does not exist, or an Ok result containing the updated post.
162+
/// </returns>
163+
[HttpPut("{postId:guid}")]
164+
public async Task<IActionResult> Update([FromRoute] Guid postId, [FromBody] UpdatePostRequest request,
165+
CancellationToken ct = default)
166+
{
167+
Guid userId = GetUserId();
168+
169+
var updatedPost = await _posts.UpdatePostAsync(
170+
postId,
171+
userId,
172+
request.Title,
173+
request.Content,
174+
ct);
175+
176+
if (updatedPost is null)
177+
{
178+
return NotFound();
179+
}
180+
181+
return Ok(updatedPost);
182+
}
183+
184+
/// <summary>
185+
/// Deletes a post specified by its unique identifier.
186+
/// </summary>
187+
/// <param name="postId">The unique identifier (GUID) of the post to delete.</param>
188+
/// <returns>
189+
/// An <see cref="IActionResult"/> indicating the result of the delete operation.
190+
/// Returns a NoContent response if the deletion is successful.
191+
/// </returns>
192+
[HttpDelete("{postId:guid}")]
193+
public async Task<IActionResult> Delete([FromRoute] Guid postId)
194+
{
195+
Guid userId = GetUserId();
196+
197+
await _posts.DeletePostAsync(postId, userId);
198+
199+
return NoContent();
200+
}
113201
}
114202
}

src/Controllers/Projects/ProjectsController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class ProjectsController : ControllerBase
1313
{
1414
private readonly IProjectService _projects;
1515

16-
private ProjectsController(IProjectService projects) => _projects = projects;
16+
public ProjectsController(IProjectService projects) => _projects = projects;
1717

1818
/// <summary>
1919
/// Retrieves a specified number of random public projects.

src/Controllers/User/AuthController.cs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using sparkly_server.DTO.Auth;
44
using sparkly_server.Services.Auth.service;
55
using sparkly_server.Services.Users;
6+
using sparkly_server.Services.Users.service;
67

78
namespace sparkly_server.Controllers.User
89
{
@@ -13,12 +14,18 @@ public class AuthController : ControllerBase
1314
private readonly IUserService _userService;
1415
private readonly IAuthService _authService;
1516

16-
private AuthController(IUserService userService, IAuthService authService)
17+
public AuthController(IUserService userService, IAuthService authService)
1718
{
1819
_userService = userService;
1920
_authService = authService;
2021
}
2122

23+
/// <summary>
24+
/// Registers a new user with the provided registration details.
25+
/// </summary>
26+
/// <param name="request">An object containing the user's username, email, and password.</param>
27+
/// <param name="ct">A cancellation token to cancel the operation if needed.</param>
28+
/// <returns>An asynchronous operation result indicating the outcome of the registration process.</returns>
2229
[AllowAnonymous]
2330
[HttpPost("register")]
2431
public async Task<IActionResult> Register([FromBody] RegisterRequest request, CancellationToken ct)
@@ -28,6 +35,12 @@ public async Task<IActionResult> Register([FromBody] RegisterRequest request, Ca
2835
return NoContent();
2936
}
3037

38+
/// <summary>
39+
/// Authenticates a user with the provided login credentials.
40+
/// </summary>
41+
/// <param name="request">An object containing the user's identifier (username or email) and password.</param>
42+
/// <param name="ct">A cancellation token to cancel the operation if needed.</param>
43+
/// <returns>An asynchronous operation result containing authentication tokens if successful, or an unauthorized response if credentials are invalid.</returns>
3144
[AllowAnonymous]
3245
[HttpPost("login")]
3346
public async Task<IActionResult> Login([FromBody] LoginRequest request, CancellationToken ct)
@@ -45,15 +58,27 @@ public async Task<IActionResult> Login([FromBody] LoginRequest request, Cancella
4558

4659
return Ok(response);
4760
}
48-
61+
62+
/// <summary>
63+
/// Logs out a user by invalidating their refresh token.
64+
/// </summary>
65+
/// <param name="request">An object containing the refresh token to be invalidated.</param>
66+
/// <param name="ct">A cancellation token to cancel the operation if needed.</param>
67+
/// <returns>An asynchronous operation result indicating the outcome of the logout process.</returns>
4968
[Authorize]
5069
[HttpPost("logout")]
5170
public async Task<IActionResult> Logout([FromBody] LogoutRequest request, CancellationToken ct)
5271
{
5372
await _authService.LogoutAsync(request.RefreshToken, ct);
5473
return NoContent();
5574
}
56-
75+
76+
/// <summary>
77+
/// Issues a new access token and refresh token pair using a valid refresh token.
78+
/// </summary>
79+
/// <param name="request">An object containing the current refresh token.</param>
80+
/// <param name="ct">A cancellation token to cancel the operation if needed.</param>
81+
/// <returns>An asynchronous result containing the newly issued tokens or an appropriate error response.</returns>
5782
[HttpPost("refresh")]
5883
[AllowAnonymous]
5984
public async Task<IActionResult> Refresh(

src/Controllers/User/ProfileController.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Microsoft.AspNetCore.Authorization;
22
using Microsoft.AspNetCore.Mvc;
33
using sparkly_server.Services.Users;
4+
using sparkly_server.Services.Users.CurrentUser;
45

56
namespace sparkly_server.Controllers.User
67
{
@@ -11,11 +12,18 @@ public class ProfileController : ControllerBase
1112
{
1213
private readonly ICurrentUser _currentUser;
1314

14-
private ProfileController(ICurrentUser currentUser)
15+
public ProfileController(ICurrentUser currentUser)
1516
{
1617
_currentUser = currentUser;
1718
}
1819

20+
/// <summary>
21+
/// Retrieves the current authenticated user's profile details such as UserId, Email, UserName, and Role.
22+
/// </summary>
23+
/// <returns>
24+
/// An HTTP 200 OK response containing the user's profile information if the user is authenticated.
25+
/// An HTTP 401 Unauthorized response if the user is not authenticated.
26+
/// </returns>
1927
[HttpGet("me")]
2028
public IActionResult Me()
2129
{

src/DTO/Posts/CreatePostRequest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
namespace sparkly_server.DTO.Posts
22
{
3-
public record CreatePostRequest(Guid ProjectId, string Title, string Content);
3+
public record CreatePostRequest(string Title, string Content);
44
}

src/DTO/Posts/Mapper/PostMapper.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ namespace sparkly_server.DTO.Posts.Mapper
55
public static class PostMapper
66
{
77
/// <summary>
8-
/// Maps a <see cref="Post"/> domain object to a <see cref="PostResponse"/> DTO.
8+
/// Converts a Post domain object into a PostResponse DTO object.
99
/// </summary>
10-
/// <param name="post">The <see cref="Post"/> instance to be mapped.</param>
11-
/// <returns>A <see cref="PostResponse"/> representing the mapped data.</returns>
12-
public static PostResponse ToResponse(this Post post)
10+
/// <param name="post">The Post domain object to convert.</param>
11+
/// <param name="currentUserId">The unique identifier of the current user to determine ownership.</param>
12+
/// <returns>A PostResponse object containing the mapped data and ownership-related properties.</returns>
13+
public static PostResponse ToResponse(this Post post, Guid currentUserId)
1314
{
15+
var isOwner = post.AuthorId == currentUserId;
16+
1417
return new PostResponse
1518
{
1619
Id = post.Id,
@@ -19,7 +22,9 @@ public static PostResponse ToResponse(this Post post)
1922
Title = post.Title,
2023
Content = post.Content,
2124
CreatedAt = post.CreatedAt,
22-
UpdatedAt = post.UpdatedAt
25+
UpdatedAt = post.UpdatedAt,
26+
CanEdit = isOwner,
27+
CanDelete = isOwner
2328
};
2429
}
2530
}

src/DTO/Posts/PostResponse.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,7 @@ public class PostResponse
99
public string Content { get; set; } = "";
1010
public DateTime CreatedAt { get; set; }
1111
public DateTime UpdatedAt { get; set; }
12+
public bool CanEdit { get; set; }
13+
public bool CanDelete { get; set; }
1214
}
1315
}

src/DTO/Posts/UpdatePostRequest.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
namespace sparkly_server.DTO.Posts
2+
{
3+
public record UpdatePostRequest(string Title, string Content);
4+
}

0 commit comments

Comments
 (0)