Commit graph

25 commits

Author SHA1 Message Date
0d7e672bc3 chore: add Docker build volume and configure OpenCode
Add a named volume for backend build artifacts to prevent root-owned files
created inside the container from blocking host Gradle builds. This follows
the same pattern as the existing backend-gradle-project volume.

Configure OpenCode with LSP, formatter, auto-compaction, and file watcher
settings for improved development experience.

Changes:
- docker-compose.yml: add backend-build:/app/backend/build volume
- opencode.json: enable lsp, formatter, auto-compaction, prune, and
  file watcher with ignore patterns for node_modules, .git, dist, build
2026-05-14 12:39:34 +02:00
8d07bb7ab1 feat: add Vue Router auth guards with admin role support
Implement client-side route protection with role-based access control. The auth
store now extracts the role claim from JWT tokens and exposes isAdmin. Router
guards enforce three levels of access: guestOnly (redirect authenticated users),
requiresAuth (redirect unauthenticated to login with redirect param), and
requiresAdmin (redirect non-admin users to home).

Changes:
- utils/jwt.ts: JWT payload parser using base64url decode (new file)
- authStore: add role ref, isAdmin computed, extractRole from JWT payload
- router: add route metadata (requiresAuth, requiresAdmin, guestOnly) and
  beforeEach guard with getActivePinia() safety for test environments
- OrdersPage.vue, AdminPage.vue: placeholder pages (new files)
- LoginPage.vue, RegisterPage.vue: use route.query.redirect after auth
- Router.spec.ts: 14 tests covering all guard scenarios
- authStore.spec.ts: tests for role extraction, isAdmin, role persistence
- LoginPage.spec.ts: test for redirect query param after login
- auth-guards.spec.ts: 7 Playwright E2E tests for guard behavior
- login.spec.ts: fix seed user credentials (test@bilhalsning.se)
2026-05-14 12:39:17 +02:00
8a95483fb8 feat: add admin role support to backend JWT authentication
Add role-based access control to the backend authentication system. The User
entity now carries a role field (default 'user'), JWT tokens include a 'role'
claim, and the login endpoint populates it from the database.

Changes:
- User entity: add role column (VARCHAR(20), default 'user') with getter/setter
- JwtService: add generateToken(email, role) overload and extractRole(token)
- AuthController: pass user.getRole() on login, 'user' on register
- Flyway V3: ALTER TABLE users ADD COLUMN role
- Flyway V4: seed admin user (admin@bilhalsning.se, role='admin')
- AuthControllerTest: add tests for admin role in token, role from DB on login
- JwtServiceTest: add tests for role extraction and default role
- UserServiceTest: assert role defaults to 'user' on createUser
2026-05-14 12:38:55 +02:00
bb4bb0c6c6 docs: add TDD policy, update Spring Boot 4 references, configure OpenCode tools
Update project documentation to reflect the Test-Driven Development
approach, Playwright E2E testing setup, and Spring Boot 4.

AGENTS.md:
- Add TDD policy section requiring tests alongside every feature PR
- Add Playwright E2E docs with local and Docker CI run commands
- Update Lombok policy: @Getter, @Setter, @NoArgsConstructor are fine
- Fix Spring Boot 3 → 4 references

CODING_GUIDELINES.md:
- Add TDD policy section mirroring AGENTS.md
- Add Playwright E2E docs in testing section
- Update Lombok policy to allow @Getter, @Setter, @NoArgsConstructor
- Fix Spring Boot 3 → 4 references

REQUIREMENTS.md:
- Fix Spring Boot 3 → 4 in tech stack, architecture diagram, and
  tech summary sections

opencode.json:
- Enable websearch and codesearch tools
2026-05-13 19:18:43 +02:00
ca21c5b659 feat: add seed test user migration
Add Flyway migration V2 that inserts a pre-seeded test user for manual
testing. This avoids having to register a new account every time the
environment is reset.

- Email: test@bilhalsning.se
- Password: test1234
- Password hash: bcrypt ($2b$12$)

The migration uses a plain INSERT (no ON CONFLICT) since it runs on
fresh databases only. H2-compatible — no PostgreSQL-specific syntax.
To re-seed after deletion: docker compose down -v && docker compose up -d
2026-05-13 19:18:19 +02:00
e05f74bd82 chore: add Docker CI compose, Gradle E2E task, and .dockerignore
Add infrastructure for running Playwright E2E tests in Docker and fix
Gradle lock conflicts between host and container builds.

Changes:
- Add docker-compose.ci.yml that starts postgres, backend, frontend,
  and a Playwright service for CI pipelines. Uses official
  mcr.microsoft.com/playwright:v1.60.0-noble image.
- Add backend-gradle-project named volume to docker-compose.yml so the
  container's .gradle/ directory is isolated from the host's. This
  prevents stale lock files from host Gradle builds (e.g. ./gradlew
  :backend:test) crashing the container's bootRun.
- Add .dockerignore excluding .gradle, .env, .git, frontend/node_modules,
  and backend/build from the Docker build context.
- Add frontendE2E Gradle task that runs npm run test:e2e:ci.
2026-05-13 19:17:55 +02:00
491dc99c55 feat: add login page with Playwright E2E tests
Add the frontend login page (LoginPage.vue) with email and password
fields, Swedish UI strings, and integration with the backend login
endpoint. Also sets up Playwright as the E2E testing framework with
browser tests for both login and registration flows.

Frontend login implementation:
- Add LoginPage.vue with form validation, error handling, and link to
  registration page
- Add login() API function in auth.ts
- Add loginUser() method to authStore that stores JWT token
- Add /logga-in route to Vue Router
- Add 'Logga in' nav link to AppHeader alongside existing 'Registrera'
- Add 10 unit tests for LoginPage component
- Add 4 unit tests for loginUser auth store method
- Add 1 route resolution test and 1 AppHeader link test

Playwright E2E setup and tests:
- Install @playwright/test and configure playwright.config.ts
- Add npm scripts: test:e2e (local) and test:e2e:ci (Docker CI)
- Exclude e2e/ directory from Vitest to prevent test runner conflicts
- Add .gitignore entries for test-results/ and playwright-report/
- Add 5 E2E tests for login (navigation, invalid credentials, success
  redirect, navigation to register, input types)
- Add 6 E2E tests for register (navigation, success redirect, validation
  errors for invalid email/short password/mismatched passwords,
  navigation to login)
2026-05-13 19:17:29 +02:00
3d4a6daee9 feat: add login endpoint with JWT authentication
Add POST /api/auth/login endpoint that authenticates users by email and
password, returning a JWT token on success. Also fixes a critical bug
where expired or malformed JWT tokens in the Authorization header caused
unhandled exceptions, crashing requests to all endpoints including public
ones like registration.

Changes:
- Add AuthController.login() endpoint with LoginRequest DTO
- Add UserService.authenticate() that validates credentials and throws
  InvalidCredentialsException on failure
- Add InvalidCredentialsException and GlobalExceptionHandler handler
  that maps it to 401 with Swedish error message
- Fix JwtAuthenticationFilter to catch JwtException (expired, malformed)
  and pass through without crashing — the filter now acts as a graceful
  enricher rather than a gatekeeper
- Add 5 controller tests for login endpoint (success, 401, validation)
- Add 4 service tests for authenticate() (success, email not found,
  password mismatch, email normalization)
- Add 2 filter tests for expired and malformed token pass-through
2026-05-13 19:16:19 +02:00
8e495672d3 feat: add user registration flow (backend + frontend)
Implement end-to-end registration: POST /api/auth/register creates a
user, returns a JWT, and the frontend RegisterPage stores the token
and redirects to home.

Backend:
- Add AuthController with POST /api/auth/register endpoint
- Add RegisterRequest record (@Email, @NotBlank, @Size(min=8))
- Add AuthResponse and ErrorResponse DTOs
- Add GlobalExceptionHandler (@RestControllerAdvice with logging)
  - EmailAlreadyExistsException -> 409 (Swedish message)
  - MethodArgumentNotValidException -> 400 (field errors)
  - Generic Exception -> 500 (Swedish message + server-side log)

Frontend:
- Add api/client.ts: centralized fetch wrapper with Bearer token
  interceptor, ApiError class, JSON error parsing
- Add api/auth.ts: register() function
- Add stores/authStore.ts: Pinia store with token persistence via
  localStorage, registerUser/logout/isAuthenticated
- Add pages/RegisterPage.vue: email + password + confirm password
  form with client-side validation, submit handler, error display,
  redirect to home on success
- Add route /registrera pointing to RegisterPage
- Add 'Registrera' link to AppHeader navigation

Infrastructure:
- Add __tests__/setup.ts: localStorage polyfill for jsdom 29
  (jsdom 29 lacks standard Storage method implementations)
- Register polyfill via vitest config setupFiles

Tests (17 new, 2 extended):
- AuthControllerTest (@SpringBootTest + @AutoConfigureMockMvc):
  5 backend tests (success 201, duplicate 409, invalid email 400,
  short password 400, missing email 400)
- authStore.spec.ts: 5 tests (unauthenticated start, localStorage
  restore, register success, register failure, logout)
- RegisterPage.spec.ts: 12 tests (render, validation, submit,
  redirect, error display, login link)
- AppHeader.spec.ts: added 'Registrera' link test
- Router.spec.ts: added /registrera route resolution test

Build: 95 tests pass (57 frontend + 38 backend), lint clean.
2026-05-01 19:37:39 +02:00
c7d443f236 docs: update README for Gradle at root, add convenience task docs
Update all references to match the new repo-root Gradle layout
after moving the wrapper out of backend/.

- Quick Start: add ./gradlew up alternative and hint at ./gradlew check
- Spring profiles: ./gradlew bootRun → ./gradlew :backend:bootRun
- Development section: add All-in-one subsection with check/up/down/reset
- Backend dev: cd backend && ./gradlew bootRun → ./gradlew :backend:bootRun
- Development vs Production table: ./gradlew bootRun → ./gradlew :backend:bootRun
- Project Structure tree: add gradlew, gradle/, settings.gradle, build.gradle
- Remove ARCHITECTURE.md reference (file never existed)
- Add Database reset section with ./gradlew reset

Also add .gradle/ and build/ to .gitignore with gradle-wrapper.jar
exception (was staged but not committed with previous refactor).
2026-05-01 18:44:05 +02:00
d70196112d refactor: move Gradle wrapper to repo root, add convenience tasks
Move gradlew, gradle/wrapper, and settings.gradle from backend/ to
the repo root so build commands run from the top-level directory.
This follows the standard multi-project Gradle layout where the build
tool lives alongside docker-compose.yml and all submodules.

- Move gradlew + gradle/wrapper/* from backend/ to repo root
- Move settings.gradle to root with rootProject.name and include 'backend'
- Create root build.gradle with convenience tasks: check, up, down, reset
- check task chains frontend lint → frontend test → backend check
- Update docker-compose.yml backend volume from ./backend:/app to .:/app
- Update backend.Dockerfile entrypoint to ./gradlew :backend:bootRun
- Update AGENTS.md: document ./gradlew check, up, down, reset
- Delete backend/settings.gradle (now at root)
- Add .gradle/ and build/ to .gitignore
- Add !gradle/wrapper/gradle-wrapper.jar exception (blocked by *.jar rule)

All 38 frontend tests and 33 backend tests pass via ./gradlew check.
2026-05-01 18:40:18 +02:00
4c6094446b feat: add app shell with header, footer, and compose flow
Add AppHeader and AppFooter to give the site a consistent chrome
around the core page content. Add ComposePage stub reachable via
"Skicka ett brev till ägaren" CTA on HomePage after vehicle lookup
succeeds. Add stub pages for about, contact, and privacy.

- Create AppHeader.vue with logo link (BilHälsning) and Hem nav link
- Create AppFooter.vue with 4 links: Om oss, Kontakt, Integritetspolicy, Villkor
- Create ComposePage.vue stub that reads plate from route query params
- Create AboutPage.vue and ContactPage.vue stub pages
- Add 4 new routes: /compose, /om, /kontakt, /integritetspolicy
- Update App.vue to render AppHeader + <main> + AppFooter around RouterView
- Add home__cta RouterLink button to HomePage, visible only when vehicle
  lookup succeeds, linking to /compose?plate=<plate>
- Remove BilHälsning h1 from HomePage (moved to header)
- Add 17 new tests: AppHeader (2), AppFooter (1), ComposePage (3),
  AboutPage (1), ContactPage (1), HomePage rewrite (6), App update (2)
- Update App.spec.ts to verify header/footer components render
2026-05-01 18:19:53 +02:00
210ac87ede feat: extract VehicleInfo component from HomePage
Move vehicle-info display logic out of HomePage into a reusable
VehicleInfo component. The component accepts vehicle, loading,
notFound, and plate props and renders the correct state with
priority: vehicle card > loading > not found. Follows the
small-page-component pattern from CODING_GUIDELINES.md.

- Create VehicleInfo.vue with 3-state v-if chain and scoped styles
- Define and export VehicleInfo interface (make/model/year/color)
- Add VehicleInfo.spec.ts with 7 tests covering all states and
  priority edge cases
- Update HomePage.vue to use VehicleInfo, replacing 3 inline
  v-if/else-if blocks with a single component tag
- Remove 5 unused CSS classes from HomePage (home__status,
  home__vehicle, home__vehicle-text, home__not-found,
  home__not-found p)
- Update AGENTS.md to require thorough commit messages with bullet
  points
2026-05-01 18:06:04 +02:00
078f07f2ac feat: add PlateInput component with Swedish plate validation and fake vehicle lookup 2026-05-01 17:38:28 +02:00
ce95a451ce feat: implement JWT authentication — service, filter, SecurityFilterChain 2026-05-01 17:38:17 +02:00
0d9baeb6e5 feat: add Subscription enum, converter, entity lifecycle hooks, and ORM-only test rule 2026-05-01 17:38:11 +02:00
c6e2e509eb chore: add JWT secret env config, jjwt deps, and docker-compose prod fixes 2026-05-01 17:38:03 +02:00
c03b5a1401 feat: add User entity, repository, service, and Flyway users table migration
- V1__create_users_table.sql replaces placeholder: creates users table with
  id UUID PK, email UNIQUE NOT NULL, password_hash NOT NULL, subscription
  VARCHAR(20) DEFAULT 'none' with CHECK constraint (none/basic/pro),
  created_at/updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP.
  Compatible with both H2 and PostgreSQL.

- SecurityConfig: minimal @Configuration providing BCryptPasswordEncoder
  bean. Required because Spring Boot 4 no longer auto-configures a
  PasswordEncoder.

- Subscription enum: NONE, BASIC, PRO with string values matching the DB
  CHECK constraint.

- User entity: @PrePersist generates UUID and timestamps in application
  code, @PreUpdate refreshes updated_at. Email setter normalizes to
  lowercase for case-insensitive uniqueness. Explicit getters/setters
  (no Lombok per guidelines).

- UserRepository: Spring Data JPA extending JpaRepository<User, UUID>.
  findByEmail(Optional) and existsByEmail for duplicate checks.

- UserService: @RequiredArgsConstructor with constructor-injected
  UserRepository and PasswordEncoder. createUser normalizes email,
  checks duplicates via existsByEmail, throws EmailAlreadyExistsException,
  hashes password with BCrypt, saves. findByEmail returns Optional<User>.

- EmailAlreadyExistsException: custom RuntimeException for duplicate
  registration attempts. ControllerAdvice handler deferred to auth ticket.

Verification: ./gradlew test passes (Flyway + H2 context loads).
docker compose up -d succeeds, Flyway applies V1 against PostgreSQL 16.
\d users confirms all columns, constraints, defaults, and indexes.
2026-05-01 02:06:24 +02:00
4d449d54d0 feat: add Docker Compose setup with dev and prod configurations
- docker-compose.yml (dev): 3 services — postgres:16, backend (gradle
  bootRun with JDK 21, spring-boot-devtools), frontend (Vite HMR on
  node:24-alpine). Source volume mounts for live editing, Gradle cache
  volume for fast rebuilds, pg_isready healthcheck on postgres.

- docker-compose.prod.yml (prod): same 3 services but with multi-stage
  Dockerfiles. Backend: Gradle bootJar → JRE Alpine, non-root user.
  Frontend: npm ci + vite build → nginx:alpine serving static dist/.
  SSL termination via self-signed cert (auto-generated in entrypoint).
  No source mounts, restart: unless-stopped, separate volumes.

- application-docker.yml: Spring profile overriding H2 with PostgreSQL
  via env vars. Hostname "postgres" resolved by Docker Compose DNS.

- Vite proxy /api → backend:8080 for dev. nginx nginx.conf handles
  /api proxy + SPA fallback + gzip + SSL in prod.

- AGENTS.md, README.md: architecture diagram, dev vs prod comparison
  table, Spring profiles docs, file reference updates.
2026-05-01 01:45:07 +02:00
9931061cb6 feat: scaffold Vue 3 + Vite frontend with TypeScript, Router, Pinia, Vitest, ESLint, Prettier
- Scaffold via npm create vite@latest --template vue-ts (create-vue interactive
  prompts require manual selection; create-vite supports non-interactive flags)
- Dependencies: vue-router (SPA routing, createWebHistory for clean URLs),
  pinia (centralised state management), vitest + @vue/test-utils + jsdom
  (unit testing with browser DOM simulation)
- Dev tooling: eslint (v10 flat config) + eslint-plugin-vue + @vue/eslint-config-typescript
  + @vue/eslint-config-prettier (ESLint-Prettier integration via vueTsConfigs),
  prettier (semi: false, singleQuote, trailingComma: all), jiti (bridges ESLint
  with TypeScript config files)
- vite.config.ts: dev server on port 3000, @ alias resolving to src/, vitest
  with jsdom environment
- eslint.config.ts: defineConfigWithVueTs wraps tseslint.config with Vue SFC
  support (vue-eslint-parser, <script setup lang="ts">), vue/multi-word off
- tsconfig.app.json: path alias @/* -> src/* for TypeScript module resolution
- src/router/index.ts: single route mapping / to HomePage
- src/pages/HomePage.vue: minimal <script setup lang="ts"> placeholder
- src/main.ts: bootstraps app with Pinia plugin + Vue Router
- src/App.vue: delegates rendering to <RouterView />
- src/__tests__/HomePage.spec.ts: smoke test verifying component mounts
- Directory structure: src/stores/, src/api/, src/composables/ with .gitkeep
  placeholders matching AGENTS.md convention (PascalCase pages, camelCase stores/composables)
- index.html: lang="sv", title BilHälsning (Swedish UI convention)
- Cleaned up: HelloWorld.vue, style.css, template boilerplate SVGs/PNGs
- Update AGENTS.md + CODING_GUIDELINES.md: .js extensions → .ts across all
  file naming examples (useXxx.ts, authStore.ts, orders.ts, client.ts)
- Verification: npm run dev serves blank page on http://localhost:3000,
  npm run lint passes (0 errors, 0 warnings), npm test passes (1 test, 1 file)
2026-05-01 00:52:38 +02:00
83b578ca22 feat: scaffold Spring Boot 4 backend with Gradle, Flyway, and H2
- Generate from Spring Initializr with Gradle Groovy DSL, Java 21, Spring Boot 4.0.6
- Dependencies: Web, Security, Data JPA, PostgreSQL Driver, Flyway, Validation, Lombok
- Add H2 runtime dependency for zero-setup local development
- Configure application.yml: H2 in-memory database, port 8080, Flyway with ddl-auto=validate
- Create placeholder Flyway migration V1__init_schema.sql
- Verify ./gradlew test passes and ./gradlew bootRun starts on port 8080
- Update AGENTS.md and README.md: Maven → Gradle commands, Spring Boot 3 → 4
2026-05-01 00:28:10 +02:00
524242bbdb chore: remove Trello integration — MCP, task tracking, csv, env vars 2026-04-30 15:48:09 +02:00
a8ee1edaf0 feat: add Trello MCP integration and env config 2026-04-30 15:41:55 +02:00
f2c1a9e2d6 docs: add branching strategy — master, develop, feature/* model 2026-04-30 15:34:00 +02:00
03010f2fd8 chore: initial project setup with docs and guidelines 2026-04-30 15:26:40 +02:00