Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,7 @@ jobs:

- name: Build Backend with Maven
run: |
mvn clean package -DskipTests

- name: Run Backend Tests
run: |
mvn test
mvn clean verify --no-transfer-progress

- name: Upload Backend Artifacts
uses: actions/upload-artifact@v4
Expand Down
209 changes: 209 additions & 0 deletions UC-01-IMPLEMENTATION-SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
# UC-01: Player Management Service - Implementation Summary

## Overview
This document summarizes the implementation of the Player Management Service backend for the Skat application, as specified in UC-01.

## Implemented Components

### 1. Database Entities (Domain Layer)
Located in: `src/main/java/com/skat/backend/domain/entities/`

- **PlayerEntity**: Represents a player with UUID id, first name, and last name
- Unique constraint on (first_name, last_name)
- Indexes on first_name and last_name

- **GameEntity**: Represents a Skat game session
- References to 3 players (player1, player2, player3) and main player
- Bid value, score, and played_at timestamp
- All player references nullable (to support player deletion)

- **PlayerScoreEntity**: Represents cumulative player scores over time
- References to player and game
- Sequence index for ordering
- Total points and created_at timestamp

### 2. Repositories (Domain Layer)
Located in: `src/main/java/com/skat/backend/domain/repositories/`

- **PlayerRepository**:
- Uniqueness checks (case-insensitive)
- Native SQL query for listing players with latest scores using PostgreSQL LATERAL joins

- **GameRepository**:
- Existence checks for player references
- Methods to nullify player references for force deletion

- **PlayerScoreRepository**:
- Existence checks for player references
- Method to nullify player references for force deletion

### 3. DTOs and Transfer Objects (Application Layer)
Located in: `src/main/java/com/skat/backend/application/dto/`

- **PlayersSort**: Enum for sorting options (NAME, SCORE_DESC)
- **PlayerTO**: Transfer object for player data (id, first_name, last_name)
- **PlayerWithScoreTO**: Player with current score snapshot
- **PlayerListResponseTO**: Response for list endpoint with items, paging, and sort
- **PagingTO**: Pagination metadata (startIndex, pageSize, total)
- **UpsertPlayerRequest**: Request DTO for create/update with validation
- **PlayersQuery**: Query parameters for list endpoint
- **ErrorResponseTO**: Standard error response format

### 4. Service Layer (Application Layer)
Located in: `src/main/java/com/skat/backend/application/`

- **PlayersService**: Interface defining service operations
- **PlayersServiceImpl**: Implementation with business logic
- List players with latest scores (sorting and pagination)
- Create player with uniqueness validation
- Update player with uniqueness validation
- Delete player with safe/force deletion modes
- Name trimming on create/update

### 5. Controller Layer (API Layer)
Located in: `src/main/java/com/skat/backend/api/controller/`

- **PlayersController**: REST endpoints
- GET /api/players - List players with query parameters
- POST /api/players - Create player
- PUT /api/players/{id} - Update player
- DELETE /api/players/{id}?forceDeletion={boolean} - Delete player

### 6. Exception Handling (API Layer)
Located in: `src/main/java/com/skat/backend/api/exception/`

- **NotFoundException**: For 404 errors
- **ConflictException**: For 409 errors
- **GlobalExceptionHandler**: @RestControllerAdvice for consistent error responses
- Handles validation errors (MethodArgumentNotValidException)
- Handles constraint violations (ConstraintViolationException)
- Handles type mismatches
- Returns standard ErrorResponseTO format

## Test Coverage

### Unit Tests (Maven Surefire)
Located in: `src/test/java/com/skat/backend/application/`

- **PlayersServiceTest**: 12 tests covering:
- Create player scenarios (success, duplicate, name trimming)
- Update player scenarios (success, not found, duplicate)
- Delete player scenarios (safe, with references, force, not found)
- List players

### Integration Tests (Maven Failsafe + Testcontainers)
Located in: `src/test/java/com/skat/backend/api/controller/`

- **PlayersControllerIT**: 19 tests covering:
- All 10 acceptance criteria from UC-01
- Additional edge cases for validation
- Full application context with PostgreSQL 18

### Test Summary
- **Total tests**: 35 (16 unit + 19 integration)
- **Status**: All passing ✅
- **Coverage**: All 10 acceptance criteria covered

## Acceptance Criteria Mapping

| AC | Description | Test(s) | Status |
|----|-------------|---------|--------|
| AC-1 | List players — default sorting and paging | given_existingPlayers_when_listPlayersWithoutParameters_then_returns200WithDefaultPaging | ✅ |
| AC-2 | List players — score_desc ordering | given_playersWithDifferentScores_when_listPlayersSortedByScore_then_returnsOrderedByScoreDesc | ✅ |
| AC-3 | List players — parameter validation | given_invalidPageSize_when_listPlayers_then_returns400 (+ 2 more) | ✅ |
| AC-4 | Create player — unique full name | given_uniquePlayerName_when_createPlayer_then_returns201WithLocation | ✅ |
| AC-5 | Create player — conflict on duplicate | given_existingPlayerName_when_createPlayerWithSameName_then_returns409 | ✅ |
| AC-6 | Update player — id must exist | given_nonExistentPlayerId_when_updatePlayer_then_returns404 | ✅ |
| AC-7 | Update player — uniqueness enforced | given_twoPlayers_when_updatePlayerToExistingName_then_returns409 | ✅ |
| AC-8 | Delete player — safe delete | given_playerWithoutReferences_when_deletePlayerWithoutForce_then_returns204 | ✅ |
| AC-9 | Delete player — conflict when referenced | given_playerReferencedInGame/Score_when_deletePlayerWithoutForce_then_returns409 | ✅ |
| AC-10 | Delete player — forced deletion | given_playerReferencedInGameAndScore_when_forceDeletePlayer_then_returns204AndNullifies | ✅ |

## Technical Decisions

1. **Java 21 & Spring Boot 3.5.6**: As specified in requirements
2. **PostgreSQL 18**: Using Testcontainers for integration tests
3. **UUID Primary Keys**: For all entities
4. **OffsetDateTime**: For all timestamp fields
5. **Bean Validation**: For request validation with jakarta.validation
6. **Native SQL Query**: For efficient player listing with latest scores using LATERAL joins
7. **Transactional Service**: All service methods properly transactional
8. **Case-insensitive Uniqueness**: For player names
9. **Name Trimming**: Automatic trimming of whitespace from names
10. **Maven Parameters Flag**: Added to compiler configuration for proper parameter name resolution

## Dependencies Added

- `spring-boot-starter-validation`: For Bean Validation support
- Maven compiler plugin configured with `-parameters` flag

## Security

- CodeQL analysis performed: **0 vulnerabilities found** ✅
- No secrets or sensitive data in code
- Proper input validation at all levels
- SQL injection protection via parameterized queries

## API Endpoints

All endpoints follow the specifications in:
- `GET_players_spec.md`
- `POST_players_upsert.md`
- `DELETE_player_spec.md`

### GET /api/players
- Query params: sort (NAME/SCORE_DESC), startIndex (≥0), pageSize (1-200)
- Returns: PlayerListResponseTO with items, paging, and sort

### POST /api/players
- Body: UpsertPlayerRequest (first_name, last_name)
- Returns: 201 Created with PlayerTO and Location header

### PUT /api/players/{id}
- Path param: id (UUID)
- Body: UpsertPlayerRequest
- Returns: 200 OK with PlayerTO

### DELETE /api/players/{id}
- Path param: id (UUID)
- Query param: forceDeletion (boolean, default false)
- Returns: 204 No Content

## Error Responses

All errors follow consistent format:
```json
{
"error": "bad_request|not_found|conflict",
"message": "Human readable message",
"field": "optional field name"
}
```

## Build & Test Commands

```bash
# Build
mvn clean compile

# Unit tests (fast)
mvn test

# Integration tests + unit tests (with Testcontainers)
mvn verify

# Clean and verify
mvn clean verify
```

## Success Criteria

✅ All 10 acceptance criteria pass
✅ Code structure follows layering (Controller → Service → Repository)
✅ DTOs/TOs match endpoint specifications
✅ All tests passing (35 tests)
✅ No security vulnerabilities (CodeQL)
✅ PostgreSQL 18 compatible
✅ Bean Validation implemented
✅ Proper error handling with consistent error responses
✅ Transactional service layer
34 changes: 34 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
version: '3.8'

services:
postgres:
image: postgres:18
container_name: postgres-db
environment:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: testdb
# Ensure Flyway scripts are executed correctly
command: >
postgres -c config_file=/var/lib/postgresql/18/docker/postgresql.conf
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql

flyway:
image: flyway/flyway:9.22.0
container_name: flyway-migrator
depends_on:
- postgres
environment:
FLYWAY_URL: jdbc:postgresql://postgres:5432/testdb
FLYWAY_USER: test
FLYWAY_PASSWORD: test
volumes:
- ./src/main/resources/db/migration:/flyway/sql
command: -connectRetries=10 migrate

volumes:
postgres-data:
driver: local
117 changes: 117 additions & 0 deletions docs/architecture/database/db_entities.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Skat Game Entities (PostgreSQL 18)

## General Design Rules
- **Database:** PostgreSQL 18
- **Primary Key:** Each entity uses a `UUID` as the primary key (`id` column).
- **Timestamps:** All date and time values are stored as `OffsetDateTime`.
- **Index Naming Convention:** `<table>_<column>_IDX`
- **Unique Index Naming Convention:** `<table>_<column>_UNIQUE`
- **Constraint Naming Convention:** `<table>_<rule>_CHK` for check constraints and `<table>_<column>_NN` for not-null constraints.
- **Character Encoding:** UTF-8, maximum length constraints applied where specified.

---

## Entity: `player`
Represents a registered player participating in Skat games.

| Column | Type | Constraints | Description |
|--------|------|--------------|--------------|
| `id` | UUID | Primary Key | Unique player identifier |
| `first_name` | VARCHAR(50) | NOT NULL (`player_first_name_NN`) | Player’s first name |
| `last_name` | VARCHAR(50) | NOT NULL (`player_last_name_NN`) | Player’s last name |

### Indexes
- `player_first_name_IDX`
- `player_last_name_IDX`

first_name and last_name mut be unique together
### Unique Constraints
- `player_first_last_name_UQ` on (`first_name`, `last_name`)
---

## Entity: `game`
Represents a single Skat game session.

| Column | Type | Constraints | Description |
|--------|------|--------------|--------------|
| `id` | UUID | Primary Key | Unique game identifier |
| `player1_id` | UUID | NULL (`game_player1_NN`), FK → `player(id)` | First player |
| `player2_id` | UUID | NULL (`game_player2_NN`), FK → `player(id)` | Second player |
| `player3_id` | UUID | NULL (`game_player3_NN`), FK → `player(id)` | Third player |
| `main_player_id` | UUID | NULL (`game_main_player_NN`), FK → `player(id)` | Player who won the bidding |
| `bid_value` | INTEGER | CHECK (`game_bid_value_CHK`) | The final bid value |
| `score` | INTEGER | CHECK (`game_score_CHK`) | The points achieved by the main player |
| `played_at` | TIMESTAMP WITH TIME ZONE | NOT NULL (`game_played_at_NN`) | Date and time when the game was played |

Player might be null, if a player is deleted from the system

### Indexes
- `game_main_player_IDX`
- `game_played_at_IDX`

---

## Entity: `player_score`
Represents cumulative player scores over time.
Each new game creates a new record for each player, forming a historical progression of player scores.

| Column | Type | Constraints | Description |
|--------|------|--------------|--------------|
| `id` | UUID | Primary Key | Unique record identifier |
| `player_id` | UUID | NULL (`player_score_player_NN`), FK → `player(id)` | The player |
| `game_id` | UUID | NOT NULL (`player_score_game_NN`), FK → `game(id)` | Reference to the game that generated the score |
| `sequence_index` | INTEGER | NOT NULL (`player_score_sequence_NN`) | Incremental index per player for sorting |
| `total_points` | INTEGER | CHECK (`player_score_total_points_CHK`) | The cumulative score after the referenced game |
| `created_at` | TIMESTAMP WITH TIME ZONE | NOT NULL (`player_score_created_at_NN`) | Timestamp when the record was created |

player_id might be null, if a player is deleted from the system
constraint sequence_index must be greater or equal 0
ter or equal 0


### Indexes
- `player_score_player_IDX`
- `player_score_game_IDX`
- `player_score_sequence_IDX`

---

## Relationship Summary
- **player** ↔ **game**: Each game references three players plus one main player.
- **player_score** ↔ **game**: Each score record links to the game that generated it.
- **player_score** ↔ **player**: Each score record belongs to a specific player.

---

## Example ER Diagram (Conceptual)

```mermaid
erDiagram
PLAYER {
UUID id PK
VARCHAR first_name
VARCHAR last_name
}
GAME {
UUID id PK
UUID player1_id FK
UUID player2_id FK
UUID player3_id FK
UUID main_player_id FK
INTEGER bid_value
INTEGER score
TIMESTAMPTZ played_at
}
PLAYER_SCORE {
UUID id PK
UUID player_id FK
UUID game_id FK
INTEGER sequence_index
INTEGER total_points
TIMESTAMPTZ created_at
}

PLAYER ||--o{ GAME : "participates in"
PLAYER ||--o{ PLAYER_SCORE : "has scores"
GAME ||--o{ PLAYER_SCORE : "produces"
```
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class ScoreServiceTest {
- Deterministic business rules, pure functions, simple orchestrations.
- Behavior that can be isolated with mocks **without** relying on Spring.
- **Choose Integration (Failsafe + @SpringBootTest + Testcontainers)** when testing:
- ** Avooid using
- Spring DI, configuration, serialization, validation, repositories, transactions.
- HTTP layer behavior, real database interactions, or cross-cutting concerns.

Expand Down
Loading