Skip to content

Commit 2f85024

Browse files
aligneddevaligneddev
andauthored
010 gas price lookup (#28)
* plan * feat: Add gas price tracking to ride records and implement gas price lookup service - Enhanced the HistoryPage to include gas price input and display. - Updated RecordRidePage to support gas price entry and validation. - Implemented a new GasPriceLookupService for fetching gas prices from an external API. - Created a GasPriceLookupEntity for caching gas prices in the database. - Added migrations to include gas price fields in the Rides table and create a GasPriceLookups table. - Updated tests to cover gas price functionality in both RecordRidePage and HistoryPage. * feat: Enhance gas price tracking with EIA source integration and update tests * feat: Add migration test coverage policy tests to ensure all migrations have corresponding test entries * remove all unused usings * user secret * fix build and using issues * Sqllite migration workaround * feat: Enhance security in Dockerfile by configuring npm to delay new publishes and block lifecycle scripts * feat: Implement SQLite migration compatibility workarounds * npm ci --ignore-scripts * fix date issues in edit ride history specs * delete playwright report * ignore pW report * user-secrets into devcontainer * aspire update * history edit price source * hmr on port 5173?? * hmr comment * fix test --------- Co-authored-by: aligneddev <aligneddev@github.com>
1 parent e3b0326 commit 2f85024

57 files changed

Lines changed: 3016 additions & 937 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.

.devcontainer/DEVCONTAINER.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,14 +136,29 @@ Note the devcontainer.json setup with
136136
},
137137
"mounts": [
138138
"source=${env:SSH_AUTH_SOCK},target=/ssh-agent,type=bind",
139-
"source=/mnt/c/Users/klogan/.ssh,target=/root/.ssh-host,type=bind,readonly"
139+
"source=${localEnv:HOME}/.ssh,target=/root/.ssh-host,type=bind,readonly",
140+
"source=${localEnv:HOME}/.microsoft/usersecrets,target=/root/.microsoft/usersecrets,type=bind"
140141
]
141142

142143
### Secrets Management
143144

144145
- Local development: Use .NET User Secrets or environment variables
145146
- CI/CD: Use GitHub repository secrets or Azure Key Vault
146147
- DevContainer environment variables in `devcontainer.json` are for non-sensitive values only
148+
- Persist User Secrets in Dev Containers by bind-mounting your host user-secrets directory to `/root/.microsoft/usersecrets`
149+
150+
Example mount paths by host OS:
151+
- Linux/macOS host: `source=${localEnv:HOME}/.microsoft/usersecrets,target=/root/.microsoft/usersecrets,type=bind`
152+
- Windows host: `source=${localEnv:APPDATA}/Microsoft/UserSecrets,target=/root/.microsoft/usersecrets,type=bind`
153+
154+
If you use Docker Compose directly, the same mapping applies:
155+
156+
```yaml
157+
services:
158+
api:
159+
volumes:
160+
- ${HOME}/.microsoft/usersecrets:/root/.microsoft/usersecrets
161+
```
147162
148163
## Advanced Customization
149164
@@ -180,7 +195,8 @@ Edit `mounts` array to add more host directories (e.g., for shared data):
180195

181196
```json
182197
"mounts": [
183-
"source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh,target=/root/.ssh,readonly",
198+
"source=${localEnv:HOME}/.ssh,target=/root/.ssh-host,type=bind,readonly",
199+
"source=${localEnv:HOME}/.microsoft/usersecrets,target=/root/.microsoft/usersecrets,type=bind",
184200
"source=/path/on/host,target=/path/in/container"
185201
]
186202
```

.devcontainer/devcontainer.Dockerfile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ RUN curl -fsSL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/s
1717
&& apt-get update \
1818
&& apt-get install -y --no-install-recommends podman nodejs \
1919
&& rm -rf /var/lib/apt/lists/* \
20-
&& npm --version
20+
&& npm --version \
21+
# Security hardening: delay installing very new publishes and block lifecycle scripts by default.
22+
&& npm config set --global min-release-age 1440 \
23+
&& npm config set --global ignore-scripts true
2124

2225
# Ensure the SDK version from global.json is available in the image.
2326
RUN if dotnet --list-sdks | grep -q "^${REQUIRED_DOTNET_SDK_VERSION}"; then \

.devcontainer/devcontainer.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "Bike Tracking - Full Stack",
2+
"name": "Bike Commuter Tracker - Full Stack",
33
"build": {
44
"dockerfile": "devcontainer.Dockerfile",
55
"context": ".."
@@ -70,6 +70,7 @@
7070
"PATH": "${containerEnv:PATH}:/usr/local/share/dotnet-tools:/root/.dotnet/tools"
7171
},
7272
"mounts": [
73-
"source=/mnt/c/Users/klogan/.ssh,target=/root/.ssh-host,type=bind,readonly"
73+
"source=${localEnv:HOME}/.ssh,target=/root/.ssh-host,type=bind,readonly",
74+
"source=/mnt/c/Users/klogan/AppData/Roaming/Microsoft/UserSecrets,target=/root/.microsoft/usersecrets,type=bind"
7475
]
7576
}

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050

5151
- name: Install frontend dependencies
5252
working-directory: src/BikeTracking.Frontend
53-
run: npm ci
53+
run: npm ci --ignore-scripts
5454

5555
- name: Install Playwright browser and system dependencies
5656
working-directory: src/BikeTracking.Frontend

.specify/memory/constitution.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ Modified Sections:
1010
- Compliance Audit Checklist: Added modular boundary and contract compatibility checks
1111
- Guardrails: Added non-negotiable interface/contract boundary rules for cross-module integration
1212
Status: Approved — modular architecture and contract-first parallel delivery are now constitutional requirements
13-
Current Update (v1.12.2): Added mandatory spec-completion gate requiring database migrations to be applied and E2E tests to pass before a spec can be marked done.
13+
Current Update (v1.12.3): Added mandatory per-migration test coverage governance requiring each migration to include a new or updated automated test, enforced by a migration coverage policy test in CI.
14+
Previous Update (v1.12.2): Added mandatory spec-completion gate requiring database migrations to be applied and E2E tests to pass before a spec can be marked done.
1415
Previous Updates:
1516
- v1.11.0: Strengthened TDD mandate with a strict gated red-green-refactor workflow requiring explicit user confirmation of failing tests before implementation.
1617
- v1.10.2: Codified a mandatory post-change verification command matrix so every change runs explicit checks before merge.
@@ -291,6 +292,7 @@ A vertical slice is **production-ready** only when all items are verified:
291292
- [ ] Post-change verification matrix executed for the impacted scope and evidence recorded
292293
- [ ] Feature branch deployed locally via `dotnet run` (entire Aspire stack: frontend, API, database)
293294
- [ ] Integration tests pass; manual E2E test via Playwright (if critical user journey)
295+
- [ ] Every migration introduced by the slice includes a new or updated automated test and an updated migration coverage policy mapping entry
294296
- [ ] All validation layers implemented: client-side (React validation), API (DTO DataAnnotations), database (constraints)
295297
- [ ] Events stored in event table with correct schema; projections materialized and queryable
296298
- [ ] Module boundaries preserved; cross-module interactions occur only via approved interfaces/contracts with compatibility evidence
@@ -362,6 +364,7 @@ Tests suggested by agent must receive explicit user approval before implementati
362364

363365
**Database Tests**
364366
- Migration up/down transitions
367+
- Migration coverage policy test must map every discovered migration to a new or updated automated test action
365368
- Event table constraints (unique EventId, non-null fields)
366369
- Foreign key integrity for aggregates
367370
- DataAnnotations constraints validated at database layer
@@ -394,6 +397,7 @@ Tests suggested by agent must receive explicit user approval before implementati
394397
14. **User Acceptance**: User validates slice meets specification and data validation rules observed
395398
15. **Phase Completion Commit**: Before starting the next phase, create a dedicated phase-completion commit that includes completed tasks and verification evidence for that phase
396399
16. **Spec Completion Gate**: Before marking any specification as done, database migrations for that spec must be applied successfully to the target local runtime database and the spec's end-to-end (Playwright) tests must run green
400+
17. **Migration Test Coverage Gate**: Every migration added or modified in a branch must include a new or updated automated test and must be represented in the migration coverage policy test map before merge
397401

398402
### Compliance Audit Checklist
399403

@@ -410,6 +414,7 @@ Tests suggested by agent must receive explicit user approval before implementati
410414
- [ ] TDD gate commits created: red baseline commit, green commit, and separate refactor commit when applicable
411415
- [ ] Phase completion commit created before moving to the next phase
412416
- [ ] Database migrations for the spec are created and applied successfully to the runtime database used for validation
417+
- [ ] Every migration introduced or modified by the spec has a corresponding new or updated automated test and a migration-coverage policy entry
413418
- [ ] Spec-level E2E (Playwright) suite executed and passing before spec marked complete
414419
- [ ] All SAMPLE_/DEMO_ data removed from code before merge
415420
- [ ] Secrets NOT committed; `.gitignore` verified; pre-commit hook prevents credential leakage
@@ -445,6 +450,7 @@ Breaking these guarantees causes architectural decay and technical debt accrual:
445450
- **TDD cycle is strictly gated and non-negotiable** — implementation code must never be written before failing tests exist, have been run, and the user has reviewed and confirmed the failures. The sequence is always: plan tests → write tests → run and prove failure → get user confirmation → implement → run after each change → verify all pass → consider refactoring. Skipping or reordering any step is prohibited.
446451
- **Commit gates are mandatory for TDD and phase transitions** — every TDD gate transition requires a commit (red, green, and refactor when performed), and every completed phase requires a dedicated phase-completion commit before proceeding.
447452
- **Spec completion requires migration + E2E gates** — a spec cannot be marked done until its database migrations are applied to the runtime database and its Playwright E2E scenarios pass.
453+
- **Every migration requires a test update** — each migration must ship with a new or updated automated test and an updated migration coverage policy entry; changes are blocked when migration coverage is incomplete.
448454
- **Expected-flow C# logic uses Result, not exceptions** — validation, not-found, conflict, and authorization business outcomes must be returned via typed Result objects (including error code/message metadata). Throwing exceptions for these expected outcomes is prohibited; exceptions are only for truly unexpected failures.
449455
- **Cross-module work is contract-first and interface-bound** — teams must integrate through explicit interfaces and versioned contracts only; direct coupling to another module's internal implementation is prohibited.
450456
- **No Entity Framework DbContext in domain layer** — domain must remain infrastructure-agnostic. If domain needs persistence logic, use repository pattern abstracting EF.

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"/^cd /workspaces/neCodeBikeTracking && pwsh \\.specify/scripts/powershell/check-prerequisites\\.ps1 -Json$/": {
1313
"approve": true,
1414
"matchCommandLine": true
15-
}
15+
},
16+
"mkdir": true
1617
},
1718
"sqltools.connections": [
1819
{

aspire.config.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"appHost": {
3+
"path": "src/BikeTracking.AppHost/BikeTracking.AppHost.csproj"
4+
}
5+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Specification Quality Checklist: Gas Price Lookup at Ride Entry
2+
3+
**Purpose**: Validate specification completeness and quality before proceeding to planning
4+
**Created**: 2026-03-31
5+
**Feature**: [spec.md](../spec.md)
6+
7+
## Content Quality
8+
9+
- [x] No implementation details (languages, frameworks, APIs)
10+
- [x] Focused on user value and business needs
11+
- [x] Written for non-technical stakeholders
12+
- [x] All mandatory sections completed
13+
14+
## Requirement Completeness
15+
16+
- [x] No [NEEDS CLARIFICATION] markers remain — FR-012 resolved: EIA API with team-managed key; secret storage deferred to a separate concern
17+
- [x] Requirements are testable and unambiguous
18+
- [x] Success criteria are measurable
19+
- [x] Success criteria are technology-agnostic (no implementation details)
20+
- [x] All acceptance scenarios are defined
21+
- [x] Edge cases are identified
22+
- [x] Scope is clearly bounded
23+
- [x] Dependencies and assumptions identified
24+
25+
## Feature Readiness
26+
27+
- [x] All functional requirements have clear acceptance criteria
28+
- [x] User scenarios cover primary flows
29+
- [x] Feature meets measurable outcomes defined in Success Criteria
30+
- [x] No implementation details leak into specification
31+
32+
## Notes
33+
34+
- FR-010 resolved: EIA API (U.S. government source) with a free team-managed API key. Secret storage mechanism (e.g., KeyVault, environment variable) is deferred and out of scope for this feature.
35+
- All items pass. The spec is ready for `/speckit.plan`.
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# API Contract: Gas Price Lookup Endpoint
2+
3+
**Feature**: 010-gas-price-lookup
4+
**Owner**: BikeTracking.Api
5+
**Consumer**: BikeTracking.Frontend
6+
7+
---
8+
9+
## GET /api/rides/gas-price
10+
11+
Retrieves the national average retail gasoline price for a given date, using a local durable cache backed by the EIA API.
12+
13+
### Request
14+
15+
```
16+
GET /api/rides/gas-price?date=YYYY-MM-DD
17+
Authorization: Bearer {token}
18+
```
19+
20+
**Query Parameters**
21+
22+
| Parameter | Type | Required | Constraints | Notes |
23+
|---|---|---|---|---|
24+
| `date` | string (YYYY-MM-DD) | Yes | Valid ISO date format | The ride date to look up the gas price for. |
25+
26+
### Response: 200 OK
27+
28+
```json
29+
{
30+
"date": "2026-03-31",
31+
"pricePerGallon": 3.1860,
32+
"isAvailable": true,
33+
"dataSource": "EIA_EPM0_NUS_Weekly"
34+
}
35+
```
36+
37+
**When unavailable** (API down, no data, future date with no coverage):
38+
```json
39+
{
40+
"date": "2100-01-01",
41+
"pricePerGallon": null,
42+
"isAvailable": false,
43+
"dataSource": null
44+
}
45+
```
46+
47+
### Response: 400 Bad Request
48+
49+
Returned when `date` is missing or not a valid date string.
50+
51+
```json
52+
{
53+
"error": "invalid_request",
54+
"message": "date query parameter is required and must be a valid date in YYYY-MM-DD format."
55+
}
56+
```
57+
58+
### Response: 401 Unauthorized
59+
60+
Returned when no valid bearer token is present.
61+
62+
### Notes
63+
64+
- This endpoint never returns a 5xx for EIA lookup failures. EIA failures are absorbed and reflected as `isAvailable: false` with `pricePerGallon: null`.
65+
- The response is deterministic for any given date: once a price is cached, the same value is always returned for that date.
66+
- The `date` parameter is used as the cache key. The actual EIA period date (the Monday of the survey week) may differ; it is not exposed in this contract.
67+
68+
---
69+
70+
## Modified Contract: GET /api/rides/defaults
71+
72+
Extends the existing defaults endpoint to include the most recent ride's gas price.
73+
74+
### Response: 200 OK (extended)
75+
76+
Adds `defaultGasPricePerGallon` to the existing response:
77+
78+
```json
79+
{
80+
"hasPreviousRide": true,
81+
"defaultRideDateTimeLocal": "2026-03-31T07:30:00",
82+
"defaultMiles": 5.2,
83+
"defaultRideMinutes": 22,
84+
"defaultTemperature": 58.0,
85+
"defaultGasPricePerGallon": 3.1860
86+
}
87+
```
88+
89+
When no previous ride exists, or the most recent ride has no gas price:
90+
```json
91+
{
92+
"hasPreviousRide": false,
93+
"defaultRideDateTimeLocal": "2026-03-31T08:00:00",
94+
"defaultGasPricePerGallon": null
95+
}
96+
```
97+
98+
**Backwards compatibility**: `defaultGasPricePerGallon` is a new nullable field. Existing clients that ignore it continue to work.
99+
100+
---
101+
102+
## Modified Contract: POST /api/rides (Record Ride)
103+
104+
Adds `gasPricePerGallon` to the existing request body.
105+
106+
### Request (extended)
107+
108+
```json
109+
{
110+
"rideDateTimeLocal": "2026-03-31T07:30:00",
111+
"miles": 5.2,
112+
"rideMinutes": 22,
113+
"temperature": 58.0,
114+
"gasPricePerGallon": 3.1860
115+
}
116+
```
117+
118+
| Field | Type | Required | Constraints |
119+
|---|---|---|---|
120+
| `gasPricePerGallon` | number | No | Must be > 0 and ≤ 999.9999 when provided. Null/omitted means unavailable. |
121+
122+
**Backwards compatibility**: Existing requests that omit `gasPricePerGallon` continue to work; the field defaults to null.
123+
124+
---
125+
126+
## Modified Contract: PUT /api/rides/{rideId} (Edit Ride)
127+
128+
Adds `gasPricePerGallon` to the existing request body.
129+
130+
### Request (extended)
131+
132+
```json
133+
{
134+
"rideDateTimeLocal": "2026-03-31T07:30:00",
135+
"miles": 5.2,
136+
"rideMinutes": 22,
137+
"temperature": 58.0,
138+
"gasPricePerGallon": 3.1860,
139+
"expectedVersion": 2
140+
}
141+
```
142+
143+
| Field | Type | Required | Constraints |
144+
|---|---|---|---|
145+
| `gasPricePerGallon` | number | No | Must be > 0 and ≤ 999.9999 when provided. Null/omitted means price not available. |
146+
147+
**Backwards compatibility**: Existing clients that omit `gasPricePerGallon` continue to work.
148+
149+
---
150+
151+
## Modified Contract: GET /api/rides/history (Ride History Row)
152+
153+
Adds `gasPricePerGallon` to each ride row in the history response.
154+
155+
### RideHistoryRow (extended)
156+
157+
```json
158+
{
159+
"rideId": 42,
160+
"rideDateTimeLocal": "2026-03-31T07:30:00",
161+
"miles": 5.2,
162+
"rideMinutes": 22,
163+
"temperature": 58.0,
164+
"gasPricePerGallon": 3.1860
165+
}
166+
```
167+
168+
**Backwards compatibility**: New nullable field; existing consumers that ignore it continue to work.

0 commit comments

Comments
 (0)