chore(scaffold): initialize git repo and monorepo with .NET solution

This commit is contained in:
Sisyphus Executor
2026-03-03 14:02:37 +01:00
commit c7dd3299d7
26 changed files with 3063 additions and 0 deletions

79
.editorconfig Normal file
View File

@@ -0,0 +1,79 @@
# EditorConfig for C# and Web projects
root = true
# All files
[*]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
charset = utf-8
# C# files
[*.cs]
indent_size = 4
# Code style rules
[*.cs]
# Indentation preferences
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_labels = flush_left
# New line preferences
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# Space preferences
csharp_space_after_cast = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_open_brace = true
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
# Wrapping preferences
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = false
# Naming conventions
[*.cs]
# Style Definitions
dotnet_naming_style.pascal_case_style.required_prefix =
dotnet_naming_style.pascal_case_style.required_suffix =
dotnet_naming_style.pascal_case_style.word_separator =
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
dotnet_naming_style.camel_case_style.required_prefix =
dotnet_naming_style.camel_case_style.required_suffix =
dotnet_naming_style.camel_case_style.word_separator =
dotnet_naming_style.camel_case_style.capitalization = camel_case
# Use PascalCase for public members
dotnet_naming_rule.public_members_pascal_case.symbols = public_symbols
dotnet_naming_rule.public_members_pascal_case.style = pascal_case_style
dotnet_naming_rule.public_members_pascal_case.severity = suggestion
dotnet_naming_symbols.public_symbols.applicable_kinds = property,method,field,event,delegate
dotnet_naming_symbols.public_symbols.applicable_accessibilities = public
# Use camelCase for private members
dotnet_naming_rule.private_members_camel_case.symbols = private_symbols
dotnet_naming_rule.private_members_camel_case.style = camel_case_style
dotnet_naming_rule.private_members_camel_case.severity = suggestion
dotnet_naming_symbols.private_symbols.applicable_kinds = property,method,field,event,delegate
dotnet_naming_symbols.private_symbols.applicable_accessibilities = private,protected

47
.gitignore vendored Normal file
View File

@@ -0,0 +1,47 @@
# .NET
bin/
obj/
*.dll
*.exe
*.pdb
*.user
*.suo
*.sln.docstates
.vs/
.vscode/
launchSettings.json
# Node
node_modules/
.next/
.cache/
dist/
build/
coverage/
.npm
.eslintcache
# IDE
.idea/
*.swp
*.swo
*~
.DS_Store
*.iml
*.sublime-workspace
# Environment
.env
.env.local
.env.*.local
# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Testing
.nyc_output/
.coverage/

10
.sisyphus/boulder.json Normal file
View File

@@ -0,0 +1,10 @@
{
"active_plan": "/Users/mastermito/Dev/opencode/.sisyphus/plans/club-work-manager.md",
"started_at": "2026-03-03T13:00:10.030Z",
"session_ids": [
"ses_3508d46e8ffeZdkOZ6IqCCwAJg"
],
"plan_name": "club-work-manager",
"agent": "atlas",
"worktree_path": "/Users/mastermito/Dev/opencode"
}

View File

@@ -0,0 +1,59 @@
# Draft: Multi-Tenant Club Work Manager
## Requirements (confirmed)
- **Multi-tenant**: Multiple clubs (tennis, cycling, etc.) share one application
- **Tenant identification**: Credential-based — user logs in, system resolves which club(s) they belong to. No subdomains or URL paths for tenant routing.
- **Data isolation**: PostgreSQL Row-Level Security (RLS) — shared database, tenant isolation via RLS policies
- **Work items**: Hybrid — both task-based (title, desc, assignee, status) AND schedule/shift-based (time slots, sign-ups, assignments)
- **Authentication**: External provider (Auth0, Clerk, or Keycloak)
- **Target scale**: MVP / Proof of concept (1-5 clubs, <100 users)
- **Backend**: .NET (ASP.NET Core) with PostgreSQL
- **Frontend**: Next.js with Bun
- **Deployment (prod)**: Kubernetes cluster
- **Deployment (local)**: Docker Compose
## Technical Decisions
- **Multi-tenancy strategy**: RLS + EF Core global query filters (defense-in-depth)
- **Tenant resolution**: NOT subdomain-based. Tenant derived from authenticated user's JWT claims (club membership)
- **Bun runtime**: Research shows Bun has P99 latency issues with Next.js SSR in production (March 2026). Use Bun for local dev / package management, Node.js for production runtime.
- **Infrastructure**: Kustomize (simpler for MVP, no Helm complexity needed yet)
## Research Findings
- **Finbuckle.MultiTenant**: Industry-standard .NET multi-tenancy library. Supports header strategy — good fit for credential-based tenant resolution.
- **EF Core Global Query Filters**: Auto-filter all queries by tenant_id. Combined with RLS for defense-in-depth.
- **PostgreSQL RLS**: SET app.current_tenant per connection. Must reset when returning connections to pool.
- **Next.js + Bun**: Bun has 340ms P99 latency vs 120ms for Node.js (Platformatic benchmarks Jan 2026). Recommendation: Bun for dev, Node.js for prod Docker image.
- **K8s deployment**: Kustomize with base + overlays (dev/staging/prod). PostgreSQL as StatefulSet for dev, managed DB for prod.
- **Docker Compose**: Hot reload via `dotnet watch` for .NET, standard Next.js dev server.
## Decisions (Round 2)
- **Auth provider**: Keycloak — self-hosted in K8s cluster, runs alongside the app
- **Multi-club membership**: YES — a user can belong to multiple clubs, needs a club-switcher/selector after login
- **Member roles**: 4-tier — Admin, Manager, Member, Viewer (per club, since user can be Admin in one club and Member in another)
- **Shift model**: Time-slot with sign-up — date, time range, location, description, required headcount. Members sign up or get assigned.
- **Repo structure**: Monorepo — /backend, /frontend, /infra in one repo
- **Notifications**: None for MVP — simplest approach, users check the app
## Decisions (Round 3)
- **.NET version**: .NET 10 (user explicitly requested)
- **Work item statuses**: 5-state — Open → Assigned → In Progress → Review → Done
- **Project type**: Greenfield — starting from scratch, empty repo
- **UI framework**: Tailwind CSS + shadcn/ui component library
## Test Strategy Decision
- **Infrastructure exists**: NO (greenfield)
- **Automated tests**: YES (TDD — tests first)
- **Backend framework**: xUnit + Testcontainers (PostgreSQL) for .NET 10
- **Frontend framework**: bun test / Vitest + React Testing Library for Next.js
- **Agent-Executed QA**: ALWAYS (mandatory — Playwright for UI, curl for API)
- **Test infrastructure setup**: Must be part of Wave 1 scaffolding tasks
## Decisions (Round 4)
- **Git repository**: Initialize git repo as first step in Task 1 — `git init` + comprehensive `.gitignore` (dotnet + node + IDE) + initial commit
## Open Questions
- (none remaining — all critical decisions made, ready for plan generation)
## Scope Boundaries
- INCLUDE: Full backend API, frontend app, Docker Compose, Kubernetes manifests (Kustomize), database schema + EF Core migrations, Keycloak integration, work item CRUD, time-slot shift management with sign-up, club-switcher, role-based access control (4 roles), PostgreSQL RLS
- EXCLUDE: Billing/subscriptions, email/push notifications, mobile app, recurring shift patterns (future), custom roles, reporting/analytics dashboard

View File

@@ -0,0 +1,5 @@
# Decisions — Club Work Manager
_Architectural choices and technical decisions made during implementation_
---

View File

@@ -0,0 +1,5 @@
# Issues — Club Work Manager
_Problems, gotchas, and edge cases discovered during implementation_
---

View File

@@ -0,0 +1,5 @@
# Learnings — Club Work Manager
_Conventions, patterns, and accumulated wisdom from task execution_
---

View File

@@ -0,0 +1,5 @@
# Problems — Club Work Manager
_Unresolved blockers requiring attention_
---

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseHttpsRedirection();
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast");
app.Run();
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WorkClub.Application\WorkClub.Application.csproj" />
<ProjectReference Include="..\WorkClub.Infrastructure\WorkClub.Infrastructure.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,6 @@
@WorkClub.Api_HostAddress = http://localhost:5142
GET {{WorkClub.Api_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1,6 @@
namespace WorkClub.Application;
public class Class1
{
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\WorkClub.Domain\WorkClub.Domain.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Finbuckle.MultiTenant" Version="8.2.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,6 @@
namespace WorkClub.Domain;
public class Class1
{
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,6 @@
namespace WorkClub.Infrastructure;
public class Class1
{
}

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\WorkClub.Domain\WorkClub.Domain.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,10 @@
namespace WorkClub.Tests.Integration;
public class UnitTest1
{
[Fact]
public void Test1()
{
}
}

View File

@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="Testcontainers.PostgreSql" Version="3.7.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WorkClub.Api\WorkClub.Api.csproj" />
<ProjectReference Include="..\WorkClub.Application\WorkClub.Application.csproj" />
<ProjectReference Include="..\WorkClub.Domain\WorkClub.Domain.csproj" />
<ProjectReference Include="..\WorkClub.Infrastructure\WorkClub.Infrastructure.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,10 @@
namespace WorkClub.Tests.Unit;
public class UnitTest1
{
[Fact]
public void Test1()
{
}
}

View File

@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WorkClub.Api\WorkClub.Api.csproj" />
<ProjectReference Include="..\WorkClub.Application\WorkClub.Application.csproj" />
<ProjectReference Include="..\WorkClub.Domain\WorkClub.Domain.csproj" />
<ProjectReference Include="..\WorkClub.Infrastructure\WorkClub.Infrastructure.csproj" />
</ItemGroup>
</Project>

8
backend/WorkClub.slnx Normal file
View File

@@ -0,0 +1,8 @@
<Solution>
<Project Path="WorkClub.Api/WorkClub.Api.csproj" />
<Project Path="WorkClub.Application/WorkClub.Application.csproj" />
<Project Path="WorkClub.Domain/WorkClub.Domain.csproj" />
<Project Path="WorkClub.Infrastructure/WorkClub.Infrastructure.csproj" />
<Project Path="WorkClub.Tests.Integration/WorkClub.Tests.Integration.csproj" />
<Project Path="WorkClub.Tests.Unit/WorkClub.Tests.Unit.csproj" />
</Solution>

6
backend/global.json Normal file
View File

@@ -0,0 +1,6 @@
{
"sdk": {
"version": "10.0.100",
"rollForward": "latestFeature"
}
}