Skip to content

Commit 940e0af

Browse files
Validate unique project name during creation and renaming, add IsProjectNameTakenAsync method, update database schema with unique index on ProjectName, and enhance API structure.
1 parent 05beb21 commit 940e0af

File tree

6 files changed

+50
-3
lines changed

6 files changed

+50
-3
lines changed

src/Controllers/Projects/ProjectsController.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
namespace sparkly_server.Controllers.Projects
1010
{
1111
[ApiController]
12+
[Route("api/v1/projects")]
1213
[Authorize]
1314
public class ProjectsController : ControllerBase
1415
{
@@ -23,6 +24,14 @@ public ProjectsController(IProjectService projects, ICurrentUser currentUser, IU
2324
_users = users;
2425
}
2526

27+
// [HttpGet("")]
28+
// public async GetProjectGlobal([FromQuery] int take = 10, CancellationToken ct = default)
29+
// {
30+
//
31+
// }
32+
33+
34+
[HttpPost("create")]
2635
public async Task<IActionResult> CreateProject([FromBody] CreateProjectRequest request)
2736
{
2837
var project = await _projects.CreateProjectAsync(request.ProjectName, request.Description, request.Visibility);
@@ -37,5 +46,13 @@ public async Task<IActionResult> CreateProject([FromBody] CreateProjectRequest r
3746

3847
return Ok(response);
3948
}
49+
50+
// [HttpPut("update/{projectId:guid}")]
51+
// public async Task<IActionResult> UpdateProject(Guid id, [FromBody] UpdateProjectRequest request, CancellationToken cn)
52+
// {
53+
// var project = await _projects.GetProjectByIdAsync(projectId: id, cn);
54+
//
55+
// project.up
56+
// }
4057
}
4158
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using sparkly_server.Enum;
2+
3+
namespace sparkly_server.DTO.Projects
4+
{
5+
public sealed record UpdateProjectRequest(
6+
string ProjectName,
7+
string Description,
8+
ProjectVisibility Visibility
9+
);
10+
}

src/Infrastructure/AppDbContext.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
7171
cfg.ToTable("projects");
7272

7373
cfg.HasKey(p => p.Id);
74+
75+
cfg.HasIndex(p => p.ProjectName)
76+
.IsUnique();
7477

7578
cfg.Property(p => p.ProjectName)
7679
.IsRequired()

src/Services/Projects/IProjectRepository.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ public interface IProjectRepository
77
Task AddAsync(Project project, CancellationToken cancellationToken = default);
88
Task<Project?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
99
Task<IReadOnlyList<Project>> GetForUserAsync(Guid userId, CancellationToken cancellationToken = default);
10-
10+
Task<bool> IsProjectNameTakenAsync(string projectName, CancellationToken cn);
11+
1112
Task SaveChangesAsync(CancellationToken cancellationToken = default);
1213
}
1314
}

src/Services/Projects/ProjectRepository.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,16 @@ public async Task<IReadOnlyList<Project>> GetForUserAsync(Guid userId, Cancellat
3232
.Where(p => p.OwnerId == userId || p.Members.Any(m => m.Id == userId))
3333
.ToListAsync(cancellationToken);
3434
}
35-
35+
public async Task<bool> IsProjectNameTakenAsync(string projectName, CancellationToken cn)
36+
{
37+
return await _db.Projects
38+
.AnyAsync(pn => pn.ProjectName == projectName, cn);
39+
}
40+
3641
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
3742
{
3843
return _db.SaveChangesAsync(cancellationToken);
3944
}
45+
4046
}
4147
}

src/Services/Projects/ProjectService.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ public async Task<Project> CreateProjectAsync(string name, string description, P
2727

2828
var owner = await _users.GetByIdAsync(userId, cancellationToken)
2929
?? throw new InvalidOperationException("Owner not found");
30+
31+
if (await _projects.IsProjectNameTakenAsync(name, cancellationToken))
32+
{
33+
throw new InvalidOperationException("ProjectName already taken.");
34+
}
3035

3136
var project = Project.Create(owner, name, description, visibility);
3237

@@ -54,12 +59,17 @@ public async Task RenameAsync(Guid projectId, string newName, CancellationToken
5459
?? throw new InvalidOperationException("User is not authenticated");
5560

5661
var project = await _projects.GetByIdAsync(projectId, cancellationToken);
57-
if (project is null)
62+
if (project is null || string.IsNullOrWhiteSpace(newName))
5863
throw new InvalidOperationException("ProjectName not found");
5964

6065
if (!project.IsOwner(userId))
6166
throw new UnauthorizedAccessException("You are not the owner of this project.");
6267

68+
if (await _projects.IsProjectNameTakenAsync(newName, cancellationToken))
69+
{
70+
throw new InvalidOperationException("ProjectName already taken.");
71+
}
72+
6373
project.Rename(newName);
6474

6575
await _projects.SaveChangesAsync(cancellationToken);

0 commit comments

Comments
 (0)