@re-shell/cli
Version:
Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja
1,326 lines (1,147 loc) • 78.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.springBootTemplate = void 0;
exports.springBootTemplate = {
id: 'spring-boot',
name: 'Spring Boot',
displayName: 'Spring Boot Web API',
description: 'Enterprise-grade Spring Boot 3.2 with security, data, web, and actuator',
version: '3.2.0',
framework: 'spring-boot',
language: 'java',
port: 8080,
tags: ['enterprise', 'mvc', 'rest', 'microservices', 'java'],
features: [
'authentication',
'authorization',
'database',
'caching',
'logging',
'monitoring',
'testing',
'documentation',
'security',
'validation',
'rate-limiting',
'cors',
'docker',
'rest-api'
],
dependencies: {},
devDependencies: {},
postInstall: [
'echo "✅ Spring Boot project created successfully!"',
'echo "📦 Installing dependencies with Maven..."',
'mvn dependency:resolve',
'echo "🚀 Start development server: mvn spring-boot:run"',
'echo "📚 API Documentation: http://localhost:{{port}}/swagger-ui.html"'
],
files: {
'pom.xml': `<?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 https://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.2.0</version>
<relativePath/>
</parent>
<groupId>{{packageName}}</groupId>
<artifactId>{{serviceName}}</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>{{serviceName}}</name>
<description>{{description}}</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2023.0.0</spring-cloud.version>
<testcontainers.version>1.19.3</testcontainers.version>
<mapstruct.version>1.5.5.Final</mapstruct.version>
<lombok.version>1.18.30</lombok.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-data-jpa</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-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Database -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
<!-- Security -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<!-- Utilities -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>\${mapstruct.version}</version>
</dependency>
<!-- Documentation -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>
<!-- Monitoring -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<!-- Development Tools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- Test Dependencies -->
<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>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>\${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>\${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.redis.testcontainers</groupId>
<artifactId>testcontainers-redis-junit</artifactId>
<version>1.6.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<version>3.0.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>\${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>\${lombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>\${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
`,
'src/main/java/{{packagePath}}/{{className}}Application.java': `package {{packageName}};
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableJpaAuditing
@EnableCaching
@EnableAsync
@EnableScheduling
public class {{className}}Application {
public static void main(String[] args) {
SpringApplication.run({{className}}Application.class, args);
}
}
`,
'src/main/java/{{packagePath}}/config/SecurityConfig.java': `package {{packageName}}.config;
import {{packageName}}.security.JwtAuthenticationEntryPoint;
import {{packageName}}.security.JwtAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
import java.util.List;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final UserDetailsService userDetailsService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(AbstractHttpConfigurer::disable)
.exceptionHandling(exception -> exception
.authenticationEntryPoint(jwtAuthenticationEntryPoint))
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/actuator/**").permitAll()
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated())
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("http://localhost:3000", "http://localhost:5173"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
`,
'src/main/java/{{packagePath}}/config/AsyncConfig.java': `package {{packageName}}.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("{{serviceName}}-async-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
`,
'src/main/java/{{packagePath}}/config/CacheConfig.java': `package {{packageName}}.config;
import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import java.time.Duration;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
return (builder) -> builder
.withCacheConfiguration("users",
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer())))
.withCacheConfiguration("products",
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer())));
}
}
`,
'src/main/java/{{packagePath}}/config/OpenApiConfig.java': `package {{packageName}}.config;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
final String securitySchemeName = "bearer-key";
return new OpenAPI()
.info(new Info()
.title("{{serviceName}} API")
.version("1.0")
.description("{{description}}")
.contact(new Contact()
.name("{{team}} Team")
.email("{{team}}@{{org}}.com"))
.license(new License()
.name("Apache 2.0")
.url("http://springdoc.org")))
.addSecurityItem(new SecurityRequirement()
.addList(securitySchemeName))
.components(new Components()
.addSecuritySchemes(securitySchemeName,
new SecurityScheme()
.name(securitySchemeName)
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")));
}
}
`,
'src/main/java/{{packagePath}}/entity/BaseEntity.java': `package {{packageName}}.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@MappedSuperclass
@Getter
@Setter
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@CreatedDate
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@Version
private Long version;
}
`,
'src/main/java/{{packagePath}}/entity/User.java': `package {{packageName}}.entity;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
@Entity
@Table(name = "users")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User extends BaseEntity implements UserDetails {
@Column(unique = true, nullable = false)
private String username;
@Column(unique = true, nullable = false)
private String email;
@Column(nullable = false)
private String password;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"))
@Column(name = "role")
@Enumerated(EnumType.STRING)
private Set<Role> roles;
@Column(nullable = false)
private boolean enabled = true;
@Column(name = "account_non_expired")
private boolean accountNonExpired = true;
@Column(name = "account_non_locked")
private boolean accountNonLocked = true;
@Column(name = "credentials_non_expired")
private boolean credentialsNonExpired = true;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.name()))
.collect(Collectors.toList());
}
@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return enabled;
}
public enum Role {
USER, ADMIN, MODERATOR
}
}
`,
'src/main/java/{{packagePath}}/repository/UserRepository.java': `package {{packageName}}.repository;
import {{packageName}}.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
boolean existsByUsername(String username);
boolean existsByEmail(String email);
@Query("SELECT u FROM User u WHERE u.username = ?1 OR u.email = ?1")
Optional<User> findByUsernameOrEmail(String usernameOrEmail);
}
`,
'src/main/java/{{packagePath}}/service/UserService.java': `package {{packageName}}.service;
import {{packageName}}.dto.UserDto;
import {{packageName}}.entity.User;
import {{packageName}}.exception.ResourceNotFoundException;
import {{packageName}}.mapper.UserMapper;
import {{packageName}}.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
@RequiredArgsConstructor
@Slf4j
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
private final UserMapper userMapper;
private final PasswordEncoder passwordEncoder;
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByUsernameOrEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
}
@Transactional(readOnly = true)
@Cacheable(value = "users", key = "#id")
public UserDto findById(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User", "id", id));
return userMapper.toDto(user);
}
@Transactional(readOnly = true)
public Page<UserDto> findAll(Pageable pageable) {
return userRepository.findAll(pageable)
.map(userMapper::toDto);
}
public UserDto create(UserDto userDto) {
if (userRepository.existsByUsername(userDto.getUsername())) {
throw new IllegalArgumentException("Username already exists");
}
if (userRepository.existsByEmail(userDto.getEmail())) {
throw new IllegalArgumentException("Email already exists");
}
User user = userMapper.toEntity(userDto);
user.setPassword(passwordEncoder.encode(userDto.getPassword()));
user = userRepository.save(user);
log.info("Created new user: {}", user.getUsername());
return userMapper.toDto(user);
}
@CacheEvict(value = "users", key = "#id")
public UserDto update(Long id, UserDto userDto) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User", "id", id));
userMapper.updateEntityFromDto(userDto, user);
user = userRepository.save(user);
log.info("Updated user: {}", user.getUsername());
return userMapper.toDto(user);
}
@CacheEvict(value = "users", key = "#id")
public void delete(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User", "id", id));
userRepository.delete(user);
log.info("Deleted user: {}", user.getUsername());
}
}
`,
'src/main/java/{{packagePath}}/controller/UserController.java': `package {{packageName}}.controller;
import {{packageName}}.dto.UserDto;
import {{packageName}}.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
@Tag(name = "User Management", description = "User management APIs")
public class UserController {
private final UserService userService;
@GetMapping
@Operation(summary = "Get all users", description = "Retrieve a paginated list of all users")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully retrieved users"),
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<Page<UserDto>> getAllUsers(
@Parameter(description = "Pagination parameters")
@PageableDefault(size = 20) Pageable pageable) {
return ResponseEntity.ok(userService.findAll(pageable));
}
@GetMapping("/{id}")
@Operation(summary = "Get user by ID", description = "Retrieve a specific user by their ID")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully retrieved user"),
@ApiResponse(responseCode = "404", description = "User not found"),
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
@PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
public ResponseEntity<UserDto> getUserById(
@Parameter(description = "User ID", required = true)
@PathVariable Long id) {
return ResponseEntity.ok(userService.findById(id));
}
@PostMapping
@Operation(summary = "Create user", description = "Create a new user")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "User created successfully"),
@ApiResponse(responseCode = "400", description = "Invalid input"),
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<UserDto> createUser(
@Parameter(description = "User data", required = true)
@Valid @RequestBody UserDto userDto) {
return new ResponseEntity<>(userService.create(userDto), HttpStatus.CREATED);
}
@PutMapping("/{id}")
@Operation(summary = "Update user", description = "Update an existing user")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "User updated successfully"),
@ApiResponse(responseCode = "404", description = "User not found"),
@ApiResponse(responseCode = "400", description = "Invalid input"),
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
@PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
public ResponseEntity<UserDto> updateUser(
@Parameter(description = "User ID", required = true)
@PathVariable Long id,
@Parameter(description = "Updated user data", required = true)
@Valid @RequestBody UserDto userDto) {
return ResponseEntity.ok(userService.update(id, userDto));
}
@DeleteMapping("/{id}")
@Operation(summary = "Delete user", description = "Delete a user by ID")
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "User deleted successfully"),
@ApiResponse(responseCode = "404", description = "User not found"),
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<Void> deleteUser(
@Parameter(description = "User ID", required = true)
@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.noContent().build();
}
}
`,
'src/main/java/{{packagePath}}/controller/AuthController.java': `package {{packageName}}.controller;
import {{packageName}}.dto.AuthRequest;
import {{packageName}}.dto.AuthResponse;
import {{packageName}}.dto.RegisterRequest;
import {{packageName}}.security.JwtService;
import {{packageName}}.service.AuthService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
@Tag(name = "Authentication", description = "Authentication APIs")
public class AuthController {
private final AuthService authService;
@PostMapping("/register")
@Operation(summary = "Register new user", description = "Register a new user account")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "User registered successfully"),
@ApiResponse(responseCode = "400", description = "Invalid input or user already exists")
})
public ResponseEntity<AuthResponse> register(
@Valid @RequestBody RegisterRequest request) {
return new ResponseEntity<>(authService.register(request), HttpStatus.CREATED);
}
@PostMapping("/login")
@Operation(summary = "Authenticate user", description = "Authenticate user and receive JWT token")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Authentication successful"),
@ApiResponse(responseCode = "401", description = "Invalid credentials")
})
public ResponseEntity<AuthResponse> authenticate(
@Valid @RequestBody AuthRequest request) {
return ResponseEntity.ok(authService.authenticate(request));
}
@PostMapping("/refresh")
@Operation(summary = "Refresh token", description = "Refresh JWT token using refresh token")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Token refreshed successfully"),
@ApiResponse(responseCode = "401", description = "Invalid refresh token")
})
public ResponseEntity<AuthResponse> refresh(
@RequestParam String refreshToken) {
return ResponseEntity.ok(authService.refreshToken(refreshToken));
}
}
`,
'src/main/java/{{packagePath}}/dto/UserDto.java': `package {{packageName}}.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.Set;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserDto {
private Long id;
@NotBlank(message = "Username is required")
@Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")
private String username;
@NotBlank(message = "Email is required")
@Email(message = "Email must be valid")
private String email;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@Size(min = 6, message = "Password must be at least 6 characters")
private String password;
private String firstName;
private String lastName;
private Set<String> roles;
private boolean enabled;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
`,
'src/main/java/{{packagePath}}/dto/AuthRequest.java': `package {{packageName}}.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AuthRequest {
@NotBlank(message = "Username or email is required")
private String username;
@NotBlank(message = "Password is required")
private String password;
}
`,
'src/main/java/{{packagePath}}/dto/AuthResponse.java': `package {{packageName}}.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AuthResponse {
private String accessToken;
private String refreshToken;
private String tokenType = "Bearer";
private Long expiresIn;
private UserDto user;
}
`,
'src/main/java/{{packagePath}}/dto/RegisterRequest.java': `package {{packageName}}.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RegisterRequest {
@NotBlank(message = "Username is required")
@Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")
private String username;
@NotBlank(message = "Email is required")
@Email(message = "Email must be valid")
private String email;
@NotBlank(message = "Password is required")
@Size(min = 6, message = "Password must be at least 6 characters")
private String password;
private String firstName;
private String lastName;
}
`,
'src/main/java/{{packagePath}}/mapper/UserMapper.java': `package {{packageName}}.mapper;
import {{packageName}}.dto.UserDto;
import {{packageName}}.entity.User;
import org.mapstruct.*;
@Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public interface UserMapper {
@Mapping(target = "roles", expression = "java(user.getRoles().stream().map(Enum::name).collect(java.util.stream.Collectors.toSet()))")
UserDto toDto(User user);
@Mapping(target = "password", ignore = true)
@Mapping(target = "roles", ignore = true)
User toEntity(UserDto userDto);
@Mapping(target = "id", ignore = true)
@Mapping(target = "password", ignore = true)
@Mapping(target = "roles", ignore = true)
void updateEntityFromDto(UserDto dto, @MappingTarget User entity);
}
`,
'src/main/java/{{packagePath}}/exception/ResourceNotFoundException.java': `package {{packageName}}.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String resourceName, String fieldName, Object fieldValue) {
super(String.format("%s not found with %s : '%s'", resourceName, fieldName, fieldValue));
}
}
`,
'src/main/java/{{packagePath}}/exception/GlobalExceptionHandler.java': `package {{packageName}}.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ProblemDetail> handleResourceNotFoundException(
ResourceNotFoundException ex, WebRequest request) {
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
HttpStatus.NOT_FOUND, ex.getMessage());
problemDetail.setTitle("Resource Not Found");
problemDetail.setProperty("timestamp", Instant.now());
problemDetail.setProperty("path", request.getDescription(false));
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(problemDetail);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ProblemDetail> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
HttpStatus.BAD_REQUEST, "Validation failed");
problemDetail.setTitle("Validation Error");
problemDetail.setProperty("errors", errors);
problemDetail.setProperty("timestamp", Instant.now());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(problemDetail);
}
@ExceptionHandler(BadCredentialsException.class)
public ResponseEntity<ProblemDetail> handleBadCredentialsException(
BadCredentialsException ex) {
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
HttpStatus.UNAUTHORIZED, "Invalid username or password");
problemDetail.setTitle("Authentication Failed");
problemDetail.setProperty("timestamp", Instant.now());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(problemDetail);
}
@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<ProblemDetail> handleAuthenticationException(
AuthenticationException ex) {
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
HttpStatus.UNAUTHORIZED, ex.getMessage());
problemDetail.setTitle("Authentication Failed");
problemDetail.setProperty("timestamp", Instant.now());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(problemDetail);
}
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<ProblemDetail> handleAccessDeniedException(
AccessDeniedException ex) {
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
HttpStatus.FORBIDDEN, "You do not have permission to access this resource");
problemDetail.setTitle("Access Denied");
problemDetail.setProperty("timestamp", Instant.now());
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(problemDetail);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ProblemDetail> handleIllegalArgumentException(
IllegalArgumentException ex) {
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
HttpStatus.BAD_REQUEST, ex.getMessage());
problemDetail.setTitle("Invalid Request");
problemDetail.setProperty("timestamp", Instant.now());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(problemDetail);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ProblemDetail> handleGlobalException(
Exception ex, WebRequest request) {
log.error("Unexpected error occurred", ex);
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
HttpStatus.INTERNAL_SERVER_ERROR, "An unexpected error occurred");
problemDetail.setTitle("Internal Server Error");
problemDetail.setProperty("timestamp", Instant.now());
problemDetail.setProperty("path", request.getDescription(false));
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(problemDetail);
}
}
`,
'src/main/java/{{packagePath}}/security/JwtService.java': `package {{packageName}}.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Service
public class JwtService {
@Value("\${application.security.jwt.secret-key}")
private String secretKey;
@Value("\${application.security.jwt.expiration}")
private long jwtExpiration;
@Value("\${application.security.jwt.refresh-token.expiration}")
private long refreshExpiration;
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
public String generateToken(UserDetails userDetails) {
return generateToken(new HashMap<>(), userDetails);
}
public String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {
return buildToken(extraClaims, userDetails, jwtExpiration);
}
public String generateRefreshToken(UserDetails userDetails) {
return buildToken(new HashMap<>(), userDetails, refreshExpiration);
}
private String buildToken(
Map<String, Object> extraClaims,
UserDetails userDetails,
long expiration
) {
return Jwts
.builder()
.setClaims(extraClaims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(getSignInKey(), SignatureAlgorithm.HS256)
.compact();
}
public boolean isTokenValid(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername())) && !isTokenExpired(token);
}
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
private Claims extractAllClaims(String token) {
return Jwts
.parserBuilder()
.setSigningKey(getSignInKey())
.build()
.parseClaimsJws(token)
.getBody();
}
private Key getSignInKey() {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
return Keys.hmacShaKeyFor(keyBytes);
}
public long getExpirationTime() {
return jwtExpiration;
}
}
`,
'src/main/java/{{packagePath}}/security/JwtAuthenticationFilter.java': `package {{packageName}}.security;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain
) throws ServletException, IOException {
if (request.getServletPath().contains("/api/auth")) {
filterChain.doFilter(request, response);
return;
}
final String authHeader = request.getHeader("Authorization");
final String jwt;
final String username;
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
jwt = authHeader.substring(7);
username = jwtService.extractUsername(jwt);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtService.isTokenValid(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
`,
'src/main/java/{{packagePath}}/security/JwtAuthenticationEntryPoint.java': `package {{packageName}}.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.http.ProblemDetail;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.time.Instant;
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ProblemDetail problemDetail = ProblemDetail.forStatus(HttpServletResponse.SC_UNAUTHORIZED);
problemDetail.setTitle("Unauthorized");
problemDetail.setDetail("Full authentication is required to access this resource");
problemDetail.setProperty("timestamp", Instant.now().toString());
problemDetail.setProperty("path", request.getRequestURI());
objectMapper.writeValue(response.getOutputStream(), problemDetail);
}
}
`,
'src/main/java/{{packagePath}}/service/AuthService.java': `package {{packageName}}.service;
import {{packageName}}.dto.*;
import {{packageName}}.entity.User;
import {{packageName}}.mapper.UserMapper;
import {{packageName}}.repository.UserRepository;
import {{packageName}}.security.JwtService;
import lombok.RequiredArgsCons