UNPKG

@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
"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