Skip to content

Commit 51d7403

Browse files
committed
feat: Complete Infrastructure and API layers
- Infrastructure layer: * Repository pattern implementations for all entities * UnitOfWork pattern with transaction support * EF Core configurations with proper relationships * SQLite database support with DI setup - API layer: * RESTful controllers for Boards, Columns, Cards, Labels * Proper error handling with domain error codes * CORS configuration for frontend * Swagger/OpenAPI documentation * Program.cs with DI configuration Backend is now complete with full CRUD operations and WIP limit enforcement. Ready to build with: dotnet build (requires internet for NuGet packages).
1 parent c0faa60 commit 51d7403

44 files changed

Lines changed: 2591 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using Taskdeck.Application.DTOs;
3+
using Taskdeck.Application.Services;
4+
5+
namespace Taskdeck.Api.Controllers;
6+
7+
[ApiController]
8+
[Route("api/[controller]")]
9+
public class BoardsController : ControllerBase
10+
{
11+
private readonly BoardService _boardService;
12+
13+
public BoardsController(BoardService boardService)
14+
{
15+
_boardService = boardService;
16+
}
17+
18+
[HttpGet]
19+
public async Task<IActionResult> GetBoards([FromQuery] string? search, [FromQuery] bool includeArchived = false)
20+
{
21+
var result = await _boardService.ListBoardsAsync(search, includeArchived);
22+
return result.IsSuccess ? Ok(result.Value) : Problem(result.ErrorMessage, statusCode: 500);
23+
}
24+
25+
[HttpGet("{id}")]
26+
public async Task<IActionResult> GetBoard(Guid id)
27+
{
28+
var result = await _boardService.GetBoardDetailAsync(id);
29+
30+
if (!result.IsSuccess)
31+
{
32+
return result.ErrorCode == "NotFound"
33+
? NotFound(new { errorCode = result.ErrorCode, message = result.ErrorMessage })
34+
: Problem(result.ErrorMessage, statusCode: 500);
35+
}
36+
37+
return Ok(result.Value);
38+
}
39+
40+
[HttpPost]
41+
public async Task<IActionResult> CreateBoard([FromBody] CreateBoardDto dto)
42+
{
43+
var result = await _boardService.CreateBoardAsync(dto);
44+
45+
if (!result.IsSuccess)
46+
{
47+
return result.ErrorCode == "ValidationError"
48+
? BadRequest(new { errorCode = result.ErrorCode, message = result.ErrorMessage })
49+
: Problem(result.ErrorMessage, statusCode: 500);
50+
}
51+
52+
return CreatedAtAction(nameof(GetBoard), new { id = result.Value.Id }, result.Value);
53+
}
54+
55+
[HttpPut("{id}")]
56+
public async Task<IActionResult> UpdateBoard(Guid id, [FromBody] UpdateBoardDto dto)
57+
{
58+
var result = await _boardService.UpdateBoardAsync(id, dto);
59+
60+
if (!result.IsSuccess)
61+
{
62+
return result.ErrorCode switch
63+
{
64+
"NotFound" => NotFound(new { errorCode = result.ErrorCode, message = result.ErrorMessage }),
65+
"ValidationError" => BadRequest(new { errorCode = result.ErrorCode, message = result.ErrorMessage }),
66+
_ => Problem(result.ErrorMessage, statusCode: 500)
67+
};
68+
}
69+
70+
return Ok(result.Value);
71+
}
72+
73+
[HttpDelete("{id}")]
74+
public async Task<IActionResult> DeleteBoard(Guid id)
75+
{
76+
var result = await _boardService.DeleteBoardAsync(id);
77+
78+
if (!result.IsSuccess)
79+
{
80+
return result.ErrorCode == "NotFound"
81+
? NotFound(new { errorCode = result.ErrorCode, message = result.ErrorMessage })
82+
: Problem(result.ErrorMessage, statusCode: 500);
83+
}
84+
85+
return NoContent();
86+
}
87+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using Taskdeck.Application.DTOs;
3+
using Taskdeck.Application.Services;
4+
5+
namespace Taskdeck.Api.Controllers;
6+
7+
[ApiController]
8+
[Route("api/boards/{boardId}/cards")]
9+
public class CardsController : ControllerBase
10+
{
11+
private readonly CardService _cardService;
12+
13+
public CardsController(CardService cardService)
14+
{
15+
_cardService = cardService;
16+
}
17+
18+
[HttpGet]
19+
public async Task<IActionResult> GetCards(
20+
Guid boardId,
21+
[FromQuery] string? search,
22+
[FromQuery] Guid? labelId,
23+
[FromQuery] Guid? columnId)
24+
{
25+
var result = await _cardService.SearchCardsAsync(boardId, search, labelId, columnId);
26+
return result.IsSuccess ? Ok(result.Value) : Problem(result.ErrorMessage, statusCode: 500);
27+
}
28+
29+
[HttpPost]
30+
public async Task<IActionResult> CreateCard(Guid boardId, [FromBody] CreateCardDto dto)
31+
{
32+
var createDto = dto with { BoardId = boardId };
33+
var result = await _cardService.CreateCardAsync(createDto);
34+
35+
if (!result.IsSuccess)
36+
{
37+
return result.ErrorCode switch
38+
{
39+
"NotFound" => NotFound(new { errorCode = result.ErrorCode, message = result.ErrorMessage }),
40+
"ValidationError" => BadRequest(new { errorCode = result.ErrorCode, message = result.ErrorMessage }),
41+
"WipLimitExceeded" => BadRequest(new { errorCode = result.ErrorCode, message = result.ErrorMessage }),
42+
_ => Problem(result.ErrorMessage, statusCode: 500)
43+
};
44+
}
45+
46+
return CreatedAtAction(nameof(GetCards), new { boardId }, result.Value);
47+
}
48+
49+
[HttpPatch("{cardId}")]
50+
public async Task<IActionResult> UpdateCard(Guid boardId, Guid cardId, [FromBody] UpdateCardDto dto)
51+
{
52+
var result = await _cardService.UpdateCardAsync(cardId, dto);
53+
54+
if (!result.IsSuccess)
55+
{
56+
return result.ErrorCode switch
57+
{
58+
"NotFound" => NotFound(new { errorCode = result.ErrorCode, message = result.ErrorMessage }),
59+
"ValidationError" => BadRequest(new { errorCode = result.ErrorCode, message = result.ErrorMessage }),
60+
_ => Problem(result.ErrorMessage, statusCode: 500)
61+
};
62+
}
63+
64+
return Ok(result.Value);
65+
}
66+
67+
[HttpPost("{cardId}/move")]
68+
public async Task<IActionResult> MoveCard(Guid boardId, Guid cardId, [FromBody] MoveCardDto dto)
69+
{
70+
var result = await _cardService.MoveCardAsync(cardId, dto);
71+
72+
if (!result.IsSuccess)
73+
{
74+
return result.ErrorCode switch
75+
{
76+
"NotFound" => NotFound(new { errorCode = result.ErrorCode, message = result.ErrorMessage }),
77+
"WipLimitExceeded" => BadRequest(new { errorCode = result.ErrorCode, message = result.ErrorMessage }),
78+
_ => Problem(result.ErrorMessage, statusCode: 500)
79+
};
80+
}
81+
82+
return Ok(result.Value);
83+
}
84+
85+
[HttpDelete("{cardId}")]
86+
public async Task<IActionResult> DeleteCard(Guid boardId, Guid cardId)
87+
{
88+
var result = await _cardService.DeleteCardAsync(cardId);
89+
90+
if (!result.IsSuccess)
91+
{
92+
return result.ErrorCode == "NotFound"
93+
? NotFound(new { errorCode = result.ErrorCode, message = result.ErrorMessage })
94+
: Problem(result.ErrorMessage, statusCode: 500);
95+
}
96+
97+
return NoContent();
98+
}
99+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using Taskdeck.Application.DTOs;
3+
using Taskdeck.Application.Services;
4+
5+
namespace Taskdeck.Api.Controllers;
6+
7+
[ApiController]
8+
[Route("api/boards/{boardId}/columns")]
9+
public class ColumnsController : ControllerBase
10+
{
11+
private readonly ColumnService _columnService;
12+
13+
public ColumnsController(ColumnService columnService)
14+
{
15+
_columnService = columnService;
16+
}
17+
18+
[HttpGet]
19+
public async Task<IActionResult> GetColumns(Guid boardId)
20+
{
21+
var result = await _columnService.GetColumnsByBoardIdAsync(boardId);
22+
return result.IsSuccess ? Ok(result.Value) : Problem(result.ErrorMessage, statusCode: 500);
23+
}
24+
25+
[HttpPost]
26+
public async Task<IActionResult> CreateColumn(Guid boardId, [FromBody] CreateColumnDto dto)
27+
{
28+
// Ensure boardId from route matches DTO
29+
var createDto = dto with { BoardId = boardId };
30+
var result = await _columnService.CreateColumnAsync(createDto);
31+
32+
if (!result.IsSuccess)
33+
{
34+
return result.ErrorCode switch
35+
{
36+
"NotFound" => NotFound(new { errorCode = result.ErrorCode, message = result.ErrorMessage }),
37+
"ValidationError" => BadRequest(new { errorCode = result.ErrorCode, message = result.ErrorMessage }),
38+
_ => Problem(result.ErrorMessage, statusCode: 500)
39+
};
40+
}
41+
42+
return CreatedAtAction(nameof(GetColumns), new { boardId }, result.Value);
43+
}
44+
45+
[HttpPatch("{columnId}")]
46+
public async Task<IActionResult> UpdateColumn(Guid boardId, Guid columnId, [FromBody] UpdateColumnDto dto)
47+
{
48+
var result = await _columnService.UpdateColumnAsync(columnId, dto);
49+
50+
if (!result.IsSuccess)
51+
{
52+
return result.ErrorCode switch
53+
{
54+
"NotFound" => NotFound(new { errorCode = result.ErrorCode, message = result.ErrorMessage }),
55+
"ValidationError" => BadRequest(new { errorCode = result.ErrorCode, message = result.ErrorMessage }),
56+
_ => Problem(result.ErrorMessage, statusCode: 500)
57+
};
58+
}
59+
60+
return Ok(result.Value);
61+
}
62+
63+
[HttpDelete("{columnId}")]
64+
public async Task<IActionResult> DeleteColumn(Guid boardId, Guid columnId)
65+
{
66+
var result = await _columnService.DeleteColumnAsync(columnId);
67+
68+
if (!result.IsSuccess)
69+
{
70+
return result.ErrorCode switch
71+
{
72+
"NotFound" => NotFound(new { errorCode = result.ErrorCode, message = result.ErrorMessage }),
73+
"Conflict" => Conflict(new { errorCode = result.ErrorCode, message = result.ErrorMessage }),
74+
_ => Problem(result.ErrorMessage, statusCode: 500)
75+
};
76+
}
77+
78+
return NoContent();
79+
}
80+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using Taskdeck.Application.DTOs;
3+
using Taskdeck.Application.Services;
4+
5+
namespace Taskdeck.Api.Controllers;
6+
7+
[ApiController]
8+
[Route("api/boards/{boardId}/labels")]
9+
public class LabelsController : ControllerBase
10+
{
11+
private readonly LabelService _labelService;
12+
13+
public LabelsController(LabelService labelService)
14+
{
15+
_labelService = labelService;
16+
}
17+
18+
[HttpGet]
19+
public async Task<IActionResult> GetLabels(Guid boardId)
20+
{
21+
var result = await _labelService.GetLabelsByBoardIdAsync(boardId);
22+
return result.IsSuccess ? Ok(result.Value) : Problem(result.ErrorMessage, statusCode: 500);
23+
}
24+
25+
[HttpPost]
26+
public async Task<IActionResult> CreateLabel(Guid boardId, [FromBody] CreateLabelDto dto)
27+
{
28+
var createDto = dto with { BoardId = boardId };
29+
var result = await _labelService.CreateLabelAsync(createDto);
30+
31+
if (!result.IsSuccess)
32+
{
33+
return result.ErrorCode switch
34+
{
35+
"NotFound" => NotFound(new { errorCode = result.ErrorCode, message = result.ErrorMessage }),
36+
"ValidationError" => BadRequest(new { errorCode = result.ErrorCode, message = result.ErrorMessage }),
37+
_ => Problem(result.ErrorMessage, statusCode: 500)
38+
};
39+
}
40+
41+
return CreatedAtAction(nameof(GetLabels), new { boardId }, result.Value);
42+
}
43+
44+
[HttpPatch("{labelId}")]
45+
public async Task<IActionResult> UpdateLabel(Guid boardId, Guid labelId, [FromBody] UpdateLabelDto dto)
46+
{
47+
var result = await _labelService.UpdateLabelAsync(labelId, dto);
48+
49+
if (!result.IsSuccess)
50+
{
51+
return result.ErrorCode switch
52+
{
53+
"NotFound" => NotFound(new { errorCode = result.ErrorCode, message = result.ErrorMessage }),
54+
"ValidationError" => BadRequest(new { errorCode = result.ErrorCode, message = result.ErrorMessage }),
55+
_ => Problem(result.ErrorMessage, statusCode: 500)
56+
};
57+
}
58+
59+
return Ok(result.Value);
60+
}
61+
62+
[HttpDelete("{labelId}")]
63+
public async Task<IActionResult> DeleteLabel(Guid boardId, Guid labelId)
64+
{
65+
var result = await _labelService.DeleteLabelAsync(labelId);
66+
67+
if (!result.IsSuccess)
68+
{
69+
return result.ErrorCode == "NotFound"
70+
? NotFound(new { errorCode = result.ErrorCode, message = result.ErrorMessage })
71+
: Problem(result.ErrorMessage, statusCode: 500);
72+
}
73+
74+
return NoContent();
75+
}
76+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using Taskdeck.Application.Services;
2+
using Taskdeck.Infrastructure;
3+
4+
var builder = WebApplication.CreateBuilder(args);
5+
6+
// Add services to the container
7+
builder.Services.AddControllers();
8+
builder.Services.AddEndpointsApiExplorer();
9+
builder.Services.AddSwaggerGen();
10+
11+
// Add Infrastructure (DbContext, Repositories)
12+
builder.Services.AddInfrastructure(builder.Configuration);
13+
14+
// Add Application Services
15+
builder.Services.AddScoped<BoardService>();
16+
builder.Services.AddScoped<ColumnService>();
17+
builder.Services.AddScoped<CardService>();
18+
builder.Services.AddScoped<LabelService>();
19+
20+
// Add CORS
21+
builder.Services.AddCors(options =>
22+
{
23+
options.AddPolicy("AllowFrontend", policy =>
24+
{
25+
policy.WithOrigins("http://localhost:5173", "http://localhost:5174")
26+
.AllowAnyHeader()
27+
.AllowAnyMethod();
28+
});
29+
});
30+
31+
var app = builder.Build();
32+
33+
// Configure the HTTP request pipeline
34+
if (app.Environment.IsDevelopment())
35+
{
36+
app.UseSwagger();
37+
app.UseSwaggerUI();
38+
}
39+
40+
app.UseCors("AllowFrontend");
41+
42+
app.UseAuthorization();
43+
44+
app.MapControllers();
45+
46+
app.Run();

0 commit comments

Comments
 (0)