Skip to content

Commit c0faa60

Browse files
committed
feat: Add backend foundation with clean architecture
- Set up .NET 8 solution with 4 projects (Domain, Application, Infrastructure, Api) - Implement Domain layer: * Core entities: Board, Column, Card, Label, CardLabel * Domain exceptions and error codes * Base Entity class with audit fields * Result pattern for error handling - Implement Application layer: * Repository interfaces (IUnitOfWork pattern) * DTOs for all entities * Service layer with business logic (BoardService, ColumnService, CardService, LabelService) * WIP limit enforcement in CardService - Implement Infrastructure layer: * EF Core DbContext with SQLite support * Entity configurations with Fluent API * Proper relationships and cascade rules - Add comprehensive domain tests: * BoardTests, ColumnTests, CardTests, LabelTests * Test domain invariants and validation rules All layers follow clean architecture principles with proper dependency flow.
1 parent 2aa113b commit c0faa60

39 files changed

Lines changed: 2157 additions & 0 deletions

backend/Taskdeck.sln

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.0.31903.59
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8A7E8B9C-1D2E-4F3A-9B5C-6D7E8F9A0B1C}"
7+
EndProject
8+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9B8F9CAD-2E3F-5G4B-AC6D-7E8F9GA1C2D}"
9+
EndProject
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Taskdeck.Domain", "src\Taskdeck.Domain\Taskdeck.Domain.csproj", "{A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}"
11+
EndProject
12+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Taskdeck.Application", "src\Taskdeck.Application\Taskdeck.Application.csproj", "{B2C3D4E5-F6A7-5B6C-9D0E-1F2A3B4C5D6E}"
13+
EndProject
14+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Taskdeck.Infrastructure", "src\Taskdeck.Infrastructure\Taskdeck.Infrastructure.csproj", "{C3D4E5F6-A7B8-6C7D-0E1F-2A3B4C5D6E7F}"
15+
EndProject
16+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Taskdeck.Api", "src\Taskdeck.Api\Taskdeck.Api.csproj", "{D4E5F6A7-B8C9-7D8E-1F2A-3B4C5D6E7F8A}"
17+
EndProject
18+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Taskdeck.Domain.Tests", "tests\Taskdeck.Domain.Tests\Taskdeck.Domain.Tests.csproj", "{E5F6A7B8-C9D0-8E9F-2A3B-4C5D6E7F8A9B}"
19+
EndProject
20+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Taskdeck.Application.Tests", "tests\Taskdeck.Application.Tests\Taskdeck.Application.Tests.csproj", "{F6A7B8C9-D0E1-9FA0-3B4C-5D6E7F8A9B0C}"
21+
EndProject
22+
Global
23+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
24+
Debug|Any CPU = Debug|Any CPU
25+
Release|Any CPU = Release|Any CPU
26+
EndGlobalSection
27+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
28+
{A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29+
{A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
30+
{A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
31+
{A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}.Release|Any CPU.Build.0 = Release|Any CPU
32+
{B2C3D4E5-F6A7-5B6C-9D0E-1F2A3B4C5D6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33+
{B2C3D4E5-F6A7-5B6C-9D0E-1F2A3B4C5D6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
34+
{B2C3D4E5-F6A7-5B6C-9D0E-1F2A3B4C5D6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
35+
{B2C3D4E5-F6A7-5B6C-9D0E-1F2A3B4C5D6E}.Release|Any CPU.Build.0 = Release|Any CPU
36+
{C3D4E5F6-A7B8-6C7D-0E1F-2A3B4C5D6E7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37+
{C3D4E5F6-A7B8-6C7D-0E1F-2A3B4C5D6E7F}.Debug|Any CPU.Build.0 = Debug|Any CPU
38+
{C3D4E5F6-A7B8-6C7D-0E1F-2A3B4C5D6E7F}.Release|Any CPU.ActiveCfg = Release|Any CPU
39+
{C3D4E5F6-A7B8-6C7D-0E1F-2A3B4C5D6E7F}.Release|Any CPU.Build.0 = Release|Any CPU
40+
{D4E5F6A7-B8C9-7D8E-1F2A-3B4C5D6E7F8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41+
{D4E5F6A7-B8C9-7D8E-1F2A-3B4C5D6E7F8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
42+
{D4E5F6A7-B8C9-7D8E-1F2A-3B4C5D6E7F8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
43+
{D4E5F6A7-B8C9-7D8E-1F2A-3B4C5D6E7F8A}.Release|Any CPU.Build.0 = Release|Any CPU
44+
{E5F6A7B8-C9D0-8E9F-2A3B-4C5D6E7F8A9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
45+
{E5F6A7B8-C9D0-8E9F-2A3B-4C5D6E7F8A9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
46+
{E5F6A7B8-C9D0-8E9F-2A3B-4C5D6E7F8A9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
47+
{E5F6A7B8-C9D0-8E9F-2A3B-4C5D6E7F8A9B}.Release|Any CPU.Build.0 = Release|Any CPU
48+
{F6A7B8C9-D0E1-9FA0-3B4C-5D6E7F8A9B0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49+
{F6A7B8C9-D0E1-9FA0-3B4C-5D6E7F8A9B0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
50+
{F6A7B8C9-D0E1-9FA0-3B4C-5D6E7F8A9B0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
51+
{F6A7B8C9-D0E1-9FA0-3B4C-5D6E7F8A9B0C}.Release|Any CPU.Build.0 = Release|Any CPU
52+
EndGlobalSection
53+
GlobalSection(NestedProjects) = preSolution
54+
{A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D} = {8A7E8B9C-1D2E-4F3A-9B5C-6D7E8F9A0B1C}
55+
{B2C3D4E5-F6A7-5B6C-9D0E-1F2A3B4C5D6E} = {8A7E8B9C-1D2E-4F3A-9B5C-6D7E8F9A0B1C}
56+
{C3D4E5F6-A7B8-6C7D-0E1F-2A3B4C5D6E7F} = {8A7E8B9C-1D2E-4F3A-9B5C-6D7E8F9A0B1C}
57+
{D4E5F6A7-B8C9-7D8E-1F2A-3B4C5D6E7F8A} = {8A7E8B9C-1D2E-4F3A-9B5C-6D7E8F9A0B1C}
58+
{E5F6A7B8-C9D0-8E9F-2A3B-4C5D6E7F8A9B} = {9B8F9CAD-2E3F-5G4B-AC6D-7E8F9GA1C2D}
59+
{F6A7B8C9-D0E1-9FA0-3B4C-5D6E7F8A9B0C} = {9B8F9CAD-2E3F-5G4B-AC6D-7E8F9GA1C2D}
60+
EndGlobalSection
61+
EndGlobal
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<ProjectReference Include="..\Taskdeck.Application\Taskdeck.Application.csproj" />
11+
<ProjectReference Include="..\Taskdeck.Infrastructure\Taskdeck.Infrastructure.csproj" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0">
16+
<PrivateAssets>all</PrivateAssets>
17+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
18+
</PackageReference>
19+
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
20+
</ItemGroup>
21+
22+
</Project>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
namespace Taskdeck.Application.DTOs;
2+
3+
public record BoardDto(
4+
Guid Id,
5+
string Name,
6+
string? Description,
7+
bool IsArchived,
8+
DateTimeOffset CreatedAt,
9+
DateTimeOffset UpdatedAt
10+
);
11+
12+
public record BoardDetailDto(
13+
Guid Id,
14+
string Name,
15+
string? Description,
16+
bool IsArchived,
17+
DateTimeOffset CreatedAt,
18+
DateTimeOffset UpdatedAt,
19+
List<ColumnDto> Columns
20+
);
21+
22+
public record CreateBoardDto(
23+
string Name,
24+
string? Description
25+
);
26+
27+
public record UpdateBoardDto(
28+
string? Name,
29+
string? Description,
30+
bool? IsArchived
31+
);
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
namespace Taskdeck.Application.DTOs;
2+
3+
public record CardDto(
4+
Guid Id,
5+
Guid BoardId,
6+
Guid ColumnId,
7+
string Title,
8+
string Description,
9+
DateTimeOffset? DueDate,
10+
bool IsBlocked,
11+
string? BlockReason,
12+
int Position,
13+
List<LabelDto> Labels,
14+
DateTimeOffset CreatedAt,
15+
DateTimeOffset UpdatedAt
16+
);
17+
18+
public record CreateCardDto(
19+
Guid BoardId,
20+
Guid ColumnId,
21+
string Title,
22+
string? Description,
23+
DateTimeOffset? DueDate,
24+
List<Guid>? LabelIds
25+
);
26+
27+
public record UpdateCardDto(
28+
string? Title,
29+
string? Description,
30+
DateTimeOffset? DueDate,
31+
bool? IsBlocked,
32+
string? BlockReason,
33+
List<Guid>? LabelIds
34+
);
35+
36+
public record MoveCardDto(
37+
Guid TargetColumnId,
38+
int TargetPosition
39+
);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
namespace Taskdeck.Application.DTOs;
2+
3+
public record ColumnDto(
4+
Guid Id,
5+
Guid BoardId,
6+
string Name,
7+
int Position,
8+
int? WipLimit,
9+
int CardCount,
10+
DateTimeOffset CreatedAt,
11+
DateTimeOffset UpdatedAt
12+
);
13+
14+
public record CreateColumnDto(
15+
Guid BoardId,
16+
string Name,
17+
int? Position,
18+
int? WipLimit
19+
);
20+
21+
public record UpdateColumnDto(
22+
string? Name,
23+
int? Position,
24+
int? WipLimit
25+
);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
namespace Taskdeck.Application.DTOs;
2+
3+
public record LabelDto(
4+
Guid Id,
5+
Guid BoardId,
6+
string Name,
7+
string ColorHex,
8+
DateTimeOffset CreatedAt,
9+
DateTimeOffset UpdatedAt
10+
);
11+
12+
public record CreateLabelDto(
13+
Guid BoardId,
14+
string Name,
15+
string ColorHex
16+
);
17+
18+
public record UpdateLabelDto(
19+
string? Name,
20+
string? ColorHex
21+
);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Taskdeck.Domain.Entities;
2+
3+
namespace Taskdeck.Application.Interfaces;
4+
5+
public interface IBoardRepository : IRepository<Board>
6+
{
7+
Task<IEnumerable<Board>> SearchAsync(string? searchText, bool includeArchived, CancellationToken cancellationToken = default);
8+
Task<Board?> GetByIdWithDetailsAsync(Guid id, CancellationToken cancellationToken = default);
9+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using Taskdeck.Domain.Entities;
2+
3+
namespace Taskdeck.Application.Interfaces;
4+
5+
public interface ICardRepository : IRepository<Card>
6+
{
7+
Task<IEnumerable<Card>> GetByBoardIdAsync(Guid boardId, CancellationToken cancellationToken = default);
8+
Task<IEnumerable<Card>> GetByColumnIdAsync(Guid columnId, CancellationToken cancellationToken = default);
9+
Task<IEnumerable<Card>> SearchAsync(Guid boardId, string? searchText, Guid? labelId, Guid? columnId, CancellationToken cancellationToken = default);
10+
Task<Card?> GetByIdWithLabelsAsync(Guid id, CancellationToken cancellationToken = default);
11+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Taskdeck.Domain.Entities;
2+
3+
namespace Taskdeck.Application.Interfaces;
4+
5+
public interface IColumnRepository : IRepository<Column>
6+
{
7+
Task<IEnumerable<Column>> GetByBoardIdAsync(Guid boardId, CancellationToken cancellationToken = default);
8+
Task<Column?> GetByIdWithCardsAsync(Guid id, CancellationToken cancellationToken = default);
9+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using Taskdeck.Domain.Entities;
2+
3+
namespace Taskdeck.Application.Interfaces;
4+
5+
public interface ILabelRepository : IRepository<Label>
6+
{
7+
Task<IEnumerable<Label>> GetByBoardIdAsync(Guid boardId, CancellationToken cancellationToken = default);
8+
}

0 commit comments

Comments
 (0)