Skip to content
Draft
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
38 changes: 38 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Maven
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar

# IDEs
.idea/
*.iws
*.iml
*.ipr
.vscode/
.classpath
.project
.settings/

# OS
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# Logs
*.log

# Other
.env
.env.local
.env.*.local
105 changes: 104 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,104 @@
# coding-agent-output-comparison
# User Management REST API

REST API for user management with JWT authentication based on the provided specifications.

## Features

- **Endpoint:** `GET /api/users/{id}`
- **Authentication:** JWT token required
- **Authorization:** Admin role only
- **Response:** JSON with user data (id, name, email, role)
- **Error Handling:** Proper HTTP status codes (200, 401, 403, 404)

## Technical Stack

- **Framework:** Spring Boot 3.1.5
- **Java Version:** 17
- **Security:** JWT tokens with HMAC-SHA384 signing
- **Database:** H2 in-memory
- **ORM:** JPA/Hibernate

## Quick Start

### 1. Build and Run

```bash
# Build and run the application
mvn spring-boot:run
```

The API will start on `http://localhost:8080`

### 2. Generate JWT Tokens

```bash
# Generate test tokens
mvn spring-boot:run -Dspring-boot.run.profiles=token-gen
```

This will output admin and user tokens for testing.

### 3. Test API Endpoints

```bash
# Test with admin token (replace {admin-token} with actual token)
curl -H "Authorization: Bearer {admin-token}" http://localhost:8080/api/users/1

# Test without token (should return 403)
curl http://localhost:8080/api/users/1

# Test with non-existent user (should return 404)
curl -H "Authorization: Bearer {admin-token}" http://localhost:8080/api/users/999
```

## API Documentation

### GET /api/users/{id}

Retrieve user by ID (Admin only).

**Parameters:**
- `id` (path) - User ID (Long, required)

**Headers:**
- `Authorization: Bearer {jwt-token}` (required)

**Responses:**

- **200 OK** - User found and returned
```json
{
"id": 1,
"name": "Admin User",
"email": "admin@example.com",
"role": "ADMIN"
}
```

- **401 Unauthorized** - Missing or invalid JWT token
- **403 Forbidden** - Insufficient permissions (non-admin user)
- **404 Not Found** - User not found
```json
{
"error": "Not Found",
"message": "User not found with id: 999",
"timestamp": "2025-09-02T07:00:00.000",
"status": 404
}
```

## Test Data

The application automatically loads test data:

1. **ID: 1** - Admin User (admin@example.com, ADMIN)
2. **ID: 2** - Regular User (user@example.com, USER)
3. **ID: 3** - Test User (test@example.com, USER)

## Database Console

For development, H2 console is available at: `http://localhost:8080/h2-console`

- **JDBC URL:** `jdbc:h2:mem:testdb`
- **Username:** `sa`
- **Password:** (empty)
93 changes: 93 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.5</version>
<relativePath/>
</parent>

<groupId>com.example</groupId>
<artifactId>user-api</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>

<name>User API</name>
<description>REST API for user management with JWT authentication</description>

<properties>
<java.version>17</java.version>
<jwt.version>0.11.5</jwt.version>
</properties>

<dependencies>
<!-- Spring Boot Starters -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jwt.version}</version>
<scope>runtime</scope>
</dependency>

<!-- Database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>

<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
11 changes: 11 additions & 0 deletions src/main/java/com/example/userapi/UserApiApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example.userapi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class UserApiApplication {
public static void main(String[] args) {
SpringApplication.run(UserApiApplication.class, args);
}
}
26 changes: 26 additions & 0 deletions src/main/java/com/example/userapi/config/DataLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.example.userapi.config;

import com.example.userapi.entity.User;
import com.example.userapi.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

@Component
@Profile("!test")
public class DataLoader implements CommandLineRunner {

@Autowired
private UserRepository userRepository;

@Override
public void run(String... args) throws Exception {
// Only load data if the repository is empty
if (userRepository.count() == 0) {
userRepository.save(new User("Admin User", "admin@example.com", "ADMIN"));
userRepository.save(new User("Regular User", "user@example.com", "USER"));
userRepository.save(new User("Test User", "test@example.com", "USER"));
}
}
}
34 changes: 34 additions & 0 deletions src/main/java/com/example/userapi/controller/UserController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.example.userapi.controller;

import com.example.userapi.dto.UserResponse;
import com.example.userapi.entity.User;
import com.example.userapi.exception.UserNotFoundException;
import com.example.userapi.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/users")
public class UserController {

@Autowired
private UserRepository userRepository;

@GetMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<UserResponse> getUserById(@PathVariable Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));

UserResponse response = new UserResponse(
user.getId(),
user.getName(),
user.getEmail(),
user.getRole()
);

return ResponseEntity.ok(response);
}
}
51 changes: 51 additions & 0 deletions src/main/java/com/example/userapi/dto/UserResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.example.userapi.dto;

public class UserResponse {
private Long id;
private String name;
private String email;
private String role;

// Constructors
public UserResponse() {}

public UserResponse(Long id, String name, String email, String role) {
this.id = id;
this.name = name;
this.email = email;
this.role = role;
}

// Getters and Setters
public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public String getRole() {
return role;
}

public void setRole(String role) {
this.role = role;
}
}
Loading