Skip to content

Commit 5aa84f5

Browse files
aligneddevaligneddev
andauthored
004 create the record ride mvp (#17)
* constitution - modularity for parallel work * plan * updated copilot-instructions * devcontainer playwright * implemented all tests pass * add db to aspire * nuget update * csharpier install with dotnet-tools * dotnet tools * Aspire.Hosting.SqlServer * update packages, aspire 13.2 * openssh dotnet tool restore * update after EF warning * podman into the container * menu * devcontainer improvements * devcontainer - playwright * can record rides, all tests are passing * CodeQL security flags were in test code * fixed e2e, .net 10.0.500 --------- Co-authored-by: aligneddev <aligneddev@github.com>
1 parent 947618a commit 5aa84f5

57 files changed

Lines changed: 4669 additions & 249 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.aspire/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"appHostPath": "..\\src\\BikeTracking.AppHost\\BikeTracking.AppHost.csproj"
2+
"appHostPath": "../src/BikeTracking.AppHost/BikeTracking.AppHost.csproj"
33
}

.config/dotnet-tools.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"version": 1,
3+
"isRoot": true,
4+
"tools": {
5+
"csharpier": {
6+
"version": "1.2.6",
7+
"commands": [
8+
"csharpier"
9+
],
10+
"rollForward": false
11+
},
12+
"dotnet-ef": {
13+
"version": "10.0.5",
14+
"commands": [
15+
"dotnet-ef"
16+
],
17+
"rollForward": false
18+
},
19+
"dotnet-outdated-tool": {
20+
"version": "4.7.1",
21+
"commands": [
22+
"dotnet-outdated"
23+
],
24+
"rollForward": false
25+
}
26+
}
27+
}

.devcontainer/DEVCONTAINER.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ This project uses a DevContainer for consistent local development across all tea
66

77
- **Image Build**: `.devcontainer/Dockerfile` (based on `mcr.microsoft.com/devcontainers/dotnet:1-10.0-noble`)
88
- **Features**: Node.js 24+ and GitHub CLI
9-
- **Post-create bootstrap**: Restores NuGet packages, runs frontend `npm ci`, and builds the solution
9+
- **Post-create bootstrap**: Configures SSH permissions, trusts dev HTTPS certs, and installs frontend dependencies
1010

1111
## Git Credentials Setup
1212

@@ -66,11 +66,11 @@ The container exports these environment variables:
6666

6767
The `postCreateCommand` runs automatically after container creation:
6868

69-
1. `dotnet restore BikeTracking.slnx`
70-
2. `npm ci --prefix src/BikeTracking.Frontend`
71-
3. `dotnet build BikeTracking.slnx`
69+
1. Copies mounted host SSH files from `/root/.ssh-host` to `/root/.ssh` and applies secure file permissions
70+
2. `dotnet dev-certs https --trust`
71+
3. `npm ci --prefix src/BikeTracking.Frontend`
7272

73-
SDK/tool installation (required .NET SDK, CSharpier, Aspire CLI) is baked into the image build in `.devcontainer/Dockerfile`, not installed at container start.
73+
SDK/tool installation and .NET dependency restore are baked into the image build in `.devcontainer/devcontainer.Dockerfile`, not installed at container start.
7474

7575
**Output**: Terminal shows progress; container is ready when build succeeds.
7676

.devcontainer/devcontainer.Dockerfile

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,21 @@ FROM mcr.microsoft.com/devcontainers/dotnet:1-10.0-noble
44

55
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
66

7+
WORKDIR /workspaces/neCodeBikeTracking
8+
9+
ENV PATH="/usr/local/share/dotnet-tools:/root/.dotnet/tools:${PATH}"
10+
711
ARG REQUIRED_DOTNET_SDK_VERSION=10.0.200
812

13+
# Install podman and Node.js 24 in a single layer.
14+
# NodeSource nodejs already includes npm; installing distro npm causes conflicts.
15+
RUN curl -fsSL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/share/keyrings/yarn-archive-keyring.gpg && apt-get update \
16+
&& curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
17+
&& apt-get update \
18+
&& apt-get install -y --no-install-recommends podman nodejs \
19+
&& rm -rf /var/lib/apt/lists/* \
20+
&& npm --version
21+
922
# Ensure the SDK version from global.json is available in the image.
1023
RUN if dotnet --list-sdks | grep -q "^${REQUIRED_DOTNET_SDK_VERSION}"; then \
1124
echo ".NET SDK ${REQUIRED_DOTNET_SDK_VERSION} already installed."; \
@@ -16,11 +29,28 @@ RUN if dotnet --list-sdks | grep -q "^${REQUIRED_DOTNET_SDK_VERSION}"; then \
1629
rm -f /tmp/dotnet-install.sh; \
1730
fi
1831

19-
# Install required CLI tools once at image build time.
20-
RUN mkdir -p /usr/local/share/dotnet-tools && \
21-
dotnet tool install csharpier --tool-path /usr/local/share/dotnet-tools
22-
32+
# Install Aspire workload early — this layer survives .csproj / manifest changes.
2333
RUN curl -fsSL https://aspire.dev/install.sh | bash
2434

25-
ENV PATH="/usr/local/share/dotnet-tools:/root/.dotnet/tools:${PATH}"
35+
# Copy dependency manifests first so restore layers can be cached.
36+
COPY global.json ./
37+
COPY BikeTracking.slnx ./
38+
COPY .config/dotnet-tools.json ./.config/dotnet-tools.json
39+
COPY src/BikeTracking.Api/BikeTracking.Api.csproj src/BikeTracking.Api/
40+
COPY src/BikeTracking.Api.Tests/BikeTracking.Api.Tests.csproj src/BikeTracking.Api.Tests/
41+
COPY src/BikeTracking.AppHost/BikeTracking.AppHost.csproj src/BikeTracking.AppHost/
42+
COPY src/BikeTracking.Domain.FSharp/BikeTracking.Domain.FSharp.fsproj src/BikeTracking.Domain.FSharp/
43+
COPY src/BikeTracking.Frontend/BikeTracking.Frontend.esproj src/BikeTracking.Frontend/
44+
COPY src/BikeTracking.ServiceDefaults/BikeTracking.ServiceDefaults.csproj src/BikeTracking.ServiceDefaults/
45+
46+
# Warm NuGet cache and install CLI tools in a single layer.
47+
RUN dotnet tool restore && dotnet restore BikeTracking.slnx
48+
49+
# Copy npm manifests and warm the npm package cache.
50+
# ~/.npm is outside the workspace bind mount, so the cache persists at runtime,
51+
# making postCreateCommand "npm ci" fast without re-downloading packages.
52+
COPY src/BikeTracking.Frontend/package.json src/BikeTracking.Frontend/package-lock.json /tmp/npm-warmup/
53+
RUN npm ci --prefix /tmp/npm-warmup \
54+
&& npm exec --prefix /tmp/npm-warmup -- playwright install \
55+
&& rm -rf /tmp/npm-warmup
2656

.devcontainer/devcontainer.json

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,14 @@
55
"context": ".."
66
},
77
"features": {
8-
"ghcr.io/devcontainers/features/node:1": {
9-
"version": "24"
10-
},
118
"ghcr.io/devcontainers/features/github-cli:1": {}
129
},
1310
"containerEnv": {
1411
"NODE_ENV": "development",
12+
"ASPIRE_CONTAINER_RUNTIME": "podman",
1513
"SSL_CERT_DIR": "/root/.aspnet/dev-certs/trust:/usr/lib/ssl/certs"
1614
},
17-
"postCreateCommand": "mkdir -p /root/.ssh && cp /root/.ssh-host/* /root/.ssh/ && chmod 700 /root/.ssh && chmod 600 /root/.ssh/config /root/.ssh/aligneddev_github /root/.ssh/omnitech_github && chmod 644 /root/.ssh/*.pub /root/.ssh/known_hosts 2>/dev/null; dotnet dev-certs https --trust && dotnet restore BikeTracking.slnx && npm ci --prefix src/BikeTracking.Frontend && dotnet build BikeTracking.slnx",
15+
"postCreateCommand": "mkdir -p /root/.ssh && cp /root/.ssh-host/* /root/.ssh/ && chmod 700 /root/.ssh && chmod 600 /root/.ssh/config /root/.ssh/aligneddev_github /root/.ssh/omnitech_github && chmod 644 /root/.ssh/*.pub /root/.ssh/known_hosts 2>/dev/null; dotnet dev-certs https --trust && npm ci --prefix src/BikeTracking.Frontend",
1816
"customizations": {
1917
"vscode": {
2018
"extensions": [
@@ -30,7 +28,10 @@
3028
"ms-vscode-remote.remote-ssh",
3129
"microsoft-aspire.aspire-vscode",
3230
"ms-dotnettools.csdevkit",
33-
"eamodio.gitlens"
31+
"eamodio.gitlens",
32+
"mtxr.sqltools",
33+
"mtxr.sqltools-driver-sqlite",
34+
"ms-playwright.playwright"
3435
],
3536
"settings": {
3637
"dotnet.defaultSolutionOrFolder": "${workspaceFolder}/BikeTracking.slnx",
@@ -50,17 +51,25 @@
5051
"editor.defaultFormatter": "esbenp.prettier-vscode",
5152
"editor.formatOnSave": true
5253
},
53-
"eslint.validate": ["javascript", "typescript", "typescriptreact"]
54+
"eslint.validate": ["javascript", "typescript", "typescriptreact"],
55+
"dotnetAcquisitionExtension.existingDotnetPath": [
56+
{
57+
"extensionId": "ms-dotnettools.csdevkit",
58+
"path": "/usr/share/dotnet"
59+
},
60+
{
61+
"extensionId": "ms-dotnettools.csharp",
62+
"path": "/usr/share/dotnet"
63+
}
64+
]
5465
}
5566
}
5667
},
5768
"remoteUser": "root",
5869
"remoteEnv": {
59-
"PATH": "${containerEnv:PATH}:/usr/local/share/dotnet-tools:/root/.dotnet/tools",
60-
"SSH_AUTH_SOCK": "/ssh-agent"
70+
"PATH": "${containerEnv:PATH}:/usr/local/share/dotnet-tools:/root/.dotnet/tools"
6171
},
6272
"mounts": [
63-
"source=${env:SSH_AUTH_SOCK},target=/ssh-agent,type=bind",
6473
"source=/mnt/c/Users/klogan/.ssh,target=/root/.ssh-host,type=bind,readonly"
6574
]
6675
}
Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,3 @@
1-
# neCodeBikeTracking Development Guidelines
1+
**This file is superseded by `.github/copilot-instructions.md` in the repository root.**
22

3-
Auto-generated from all feature plans. Last updated: 2026-03-13
4-
5-
## Active Technologies
6-
- TypeScript 5.x (React 19 + Vite); .NET 10 C# / F# backend (unchanged) + `react-router-dom` v7 (new); React 19, Vite, ASP.NET Core Minimal API (existing) (003-user-login)
7-
- `sessionStorage` (client-side auth session only); SQLite via EF Core (existing, unchanged) (003-user-login)
8-
9-
- .NET 10 (C#), F# (domain project), TypeScript 5.x (Aurelia 2 frontend) + ASP.NET Core Minimal API, Microsoft Aspire AppHost, Entity Framework Core + SQLite provider, Aurelia 2 + `@aurelia/router`, .NET `System.Security.Cryptography` (PBKDF2), background worker for outbox retry (001-user-signup-pin)
10-
11-
## Project Structure
12-
13-
```text
14-
backend/
15-
frontend/
16-
tests/
17-
```
18-
19-
## Commands
20-
21-
npm test; npm run lint
22-
23-
## Code Style
24-
25-
.NET 10 (C#), F# (domain project), TypeScript 5.x (Aurelia 2 frontend): Follow standard conventions
26-
27-
## Recent Changes
28-
- 003-user-login: Added TypeScript 5.x (React 19 + Vite); .NET 10 C# / F# backend (unchanged) + `react-router-dom` v7 (new); React 19, Vite, ASP.NET Core Minimal API (existing)
29-
30-
- 001-user-signup-pin: Added .NET 10 (C#), F# (domain project), TypeScript 5.x (Aurelia 2 frontend) + ASP.NET Core Minimal API, Microsoft Aspire AppHost, Entity Framework Core + SQLite provider, Aurelia 2 + `@aurelia/router`, .NET `System.Security.Cryptography` (PBKDF2), background worker for outbox retry
31-
32-
<!-- MANUAL ADDITIONS START -->
33-
<!-- MANUAL ADDITIONS END -->
3+
See `.github/copilot-instructions.md` for the current Copilot development guide.

.github/copilot-instructions.md

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Bike Tracking: Copilot Development Guide
2+
3+
Local-first commute tracking app using .NET Aspire, Minimal API, F# domain, and React 19 frontend. Built for end-user machines (SQLite-based, no cloud infrastructure required).
4+
5+
## Quick Setup
6+
7+
**Mandatory: Use DevContainer** (all tooling pre-configured).
8+
- `Ctrl+Shift+P` → "Dev Containers: Open Folder in Container"
9+
- Once connected, all dependencies ready (.NET 10 SDK, Node 24+, npm, CSharpier)
10+
11+
**Start the app:**
12+
```bash
13+
dotnet run --project src/BikeTracking.AppHost
14+
```
15+
Aspire Dashboard opens at http://localhost:19629 — launch frontend and API from there.
16+
17+
## Commands
18+
19+
### Backend (.NET 10 / C# / F#)
20+
- **Full solution tests:** `dotnet test BikeTracking.slnx`
21+
- **Single test project:** `dotnet test src/BikeTracking.Api.Tests/BikeTracking.Api.Tests.csproj`
22+
- **Restore deps:** `dotnet restore BikeTracking.slnx`
23+
- **Code formatting:** `csharpier format .` (run from repo root; required before commits)
24+
- **Watch mode (API):** `dotnet watch --project src/BikeTracking.Api` (auto-rebuild on changes)
25+
26+
### Frontend (TypeScript / React 19 / Vite)
27+
From `src/BikeTracking.Frontend`:
28+
- **Dev server:** `npm run dev` (HMR on http://localhost:5173)
29+
- **Build:** `npm run build`
30+
- **Lint:** `npm run lint` (ESLint + Stylelint)
31+
- **Unit tests:** `npm run test:unit` (Vitest; use `--ui` flag for interactive mode)
32+
- **E2E tests:** `npm run test:e2e` (Playwright; runs against live API/DB)
33+
- **Watch unit tests:** `npm run test:unit:watch`
34+
35+
### CI Validation
36+
- **Full CI pipeline:** Run locally before pushing: `dotnet test BikeTracking.slnx && cd src/BikeTracking.Frontend && npm run lint && npm run build && npm run test:unit && npm run test:e2e`
37+
- Tests are run in `.github/workflows/ci.yaml` on all PRs and pushes to main
38+
39+
## Architecture
40+
41+
### Projects
42+
- **BikeTracking.AppHost** — .NET Aspire orchestration; starts API, frontend, and dashboard for local dev
43+
- **BikeTracking.Api** — Minimal API (C#); handles routes, EF Core migrations, outbox publishing
44+
- **BikeTracking.Api.Tests** — xUnit backend tests
45+
- **BikeTracking.Domain.FSharp** — Domain logic (F#): discriminated unions for events, pure functions for state transitions, immutable value objects
46+
- **BikeTracking.Frontend** — React 19 + Vite + TypeScript; form-driven UI with react-router-dom v7 for navigation
47+
- **BikeTracking.ServiceDefaults** — Shared Aspire telemetry and OpenTelemetry wiring (all services configured via this)
48+
49+
### Data Model
50+
- **SQLite** (local user-machine deployment; no database service needed)
51+
- EF Core Code-First migrations auto-applied on startup
52+
- **Outbox pattern**: All domain events written to outbox table; background worker retries with progressive delay (up to 30s) until published
53+
- **Event sourcing**: User registration, login, ride records are immutable events; current state derived from event history
54+
55+
### Layering
56+
57+
**F# Domain Layer (BikeTracking.Domain.FSharp):**
58+
- Pure functions: state transitions, calculations
59+
- Discriminated unions: enforce valid event and command structures
60+
- Immutable types: all domain state immutable by default
61+
- No I/O; no dependencies; trivially testable
62+
63+
**C# API Layer (BikeTracking.Api):**
64+
- Receives commands from frontend (JSON), validates with data annotations
65+
- Calls F# domain functions; receives immutable events
66+
- Writes events to SQLite outbox; EF Core persists
67+
- Minimal API endpoints (no controllers)
68+
- Dependency injection wired via Aspire
69+
70+
**Frontend (React/TypeScript):**
71+
- Form-driven signup (name + PIN), login identify screen
72+
- Client-side validation (required fields, format)
73+
- Server-side validation enforced by API (defense-in-depth)
74+
- sessionStorage for auth tokens (not persisted to disk)
75+
76+
## Key Conventions & Patterns
77+
78+
### Test-Driven Development (TDD)
79+
- **Mandatory red-green-refactor cycle**: Write failing tests first, confirm failure with user, implement, validate all green
80+
- Backend tests (xUnit) target pure domain logic (F#) — 85%+ coverage expected
81+
- Frontend tests: unit tests (Vitest) for components; E2E tests (Playwright) for full-stack signup/login flow against live API
82+
- E2E tests use SQLite DB, throw data away after each test (integration-like behavior)
83+
- Always validate test failures before implementation (prove tests are meaningful, not vacuous)
84+
85+
### F# Domain Patterns
86+
- **Railway Oriented Programming (Result<'T>)**: All domain functions return `Result<'T, Error>` for explicit error handling
87+
- **Discriminated unions**: Commands and Events use DUs to enforce valid states (no invalid combinations)
88+
- **Active patterns**: Optional patterns for complex state matching
89+
- **Immutable records**: All domain types immutable; constructors enforce invariants
90+
- Reference F# docs: https://fsharp.org/
91+
92+
### C# API Patterns
93+
- **Data annotations** on request DTOs: [Required], [StringLength], [EmailAddress], etc. (enforced by ASP.NET Core)
94+
- **Minimal API endpoints**: Map HTTP verbs directly; no controller classes
95+
- **Dependency injection**: Services registered in Program.cs; Aspire handles wiring
96+
- **EF Core DbContext**: QueryTimeout for long-running queries; migrations auto-applied on startup
97+
- Reference: https://learn.microsoft.com/aspnet/core/fundamentals/minimal-apis
98+
99+
### React / TypeScript Frontend
100+
- **React 19 patterns**: Hooks (useState, useEffect), functional components only
101+
- **React Router v7**: useNavigate() for programmatic navigation, useParams() for route params
102+
- **Form handling**: Uncontrolled forms (ref-based) or controlled components (useState); validate before submit
103+
- **Client-side validation**: Required fields, format checks; must match server-side rules
104+
- **No inline CSS**: Use .css files with Stylelint + ESLint rules; import in component
105+
- **Component organization**: One component per file; descriptive PascalCase names; co-locate tests with components
106+
- Reference: https://react.dev/
107+
108+
**TypeScript Type Safety (Critical):**
109+
- **NO `any` types allowed** — use explicit types for all variables, parameters, return values
110+
- **Component props**: Define explicit interface (e.g., `interface SignupFormProps { onSuccess: () => void; }`)
111+
- **API contracts**: Define TypeScript types matching backend DTOs (e.g., `interface UserSignupRequest { name: string; pin: string; }`)
112+
- **React hooks**: Type hook parameters and return values (e.g., `const [name, setName] = useState<string>('')`)
113+
- **Form refs**: Type refs explicitly (e.g., `useRef<HTMLInputElement>(null)`)
114+
- **Event handlers**: Type event objects (e.g., `(e: React.FormEvent<HTMLFormElement>) => void`)
115+
- **Route params**: Define param types (e.g., `interface RouteParams { userId: string; }` then `useParams<RouteParams>()`)
116+
- **Service functions**: Return typed Promises (e.g., `async function identifyUser(name: string, pin: string): Promise<User> { ... }`)
117+
- Use `unknown` only when truly dynamic; narrow with type guards
118+
- Use discriminated unions for state variants (e.g., `type PageState = { status: 'loading' } | { status: 'success'; data: User } | { status: 'error'; message: string }`)
119+
- Reference TypeScript handbook: https://www.typescriptlang.org/docs/
120+
121+
### Event Sourcing & Outbox
122+
- **No direct side effects in domain**: Domain functions are pure; I/O happens at API layer
123+
- **Events are immutable**: Once persisted, events never change; corrections via new events
124+
- **Outbox table**: Every API write also writes to outbox; background job publishes with exponential backoff
125+
- **Idempotency**: Handlers must be safe to re-execute (outbox may retry)
126+
127+
## Local Development Deployment
128+
129+
- **Target deployment**: Local user machines (Windows, macOS, Linux)
130+
- **Database**: SQLite file (default: `biketracking.local.db` in app root; move to user-writable app-data folder for packaged installs)
131+
- **Pre-install safety**: Before schema upgrades, users should back up the SQLite file
132+
- **No separate services needed**: No Docker, no separate database server, no cloud provider required
133+
- **Multi-user setup**: For multi-user requirements on a single machine, consider SQL Server LocalDB or SQL Server Express (future phase)

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,4 @@ src/.vs/*
6363
.fake
6464
src/BikeTracking.Frontend/test-results/
6565
src/BikeTracking.Frontend/playwright-report/
66+
src/BikeTracking.Frontend/playwright-report/index.html

0 commit comments

Comments
 (0)