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,585 lines (1,314 loc) 54.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.micronautTemplate = void 0; exports.micronautTemplate = { id: 'micronaut', name: 'Micronaut', displayName: 'Micronaut Framework', description: 'Modern JVM-based framework with dependency injection, AOP, and cloud-native features', version: '4.2.0', framework: 'micronaut', language: 'java', port: 8080, tags: ['microservices', 'cloud-native', 'reactive', 'dependency-injection', 'aop'], features: [ 'authentication', 'authorization', 'database', 'caching', 'logging', 'monitoring', 'testing', 'documentation', 'security', 'validation', 'rest-api', 'graphql', 'microservices', 'docker' ], dependencies: {}, devDependencies: {}, postInstall: [ 'echo "✅ Micronaut project created successfully!"', 'echo "📦 Installing dependencies..."', './mvnw clean compile', 'echo "🚀 Start development server: ./mvnw mn:run"', 'echo "📚 API Documentation: http://localhost:{{port}}/swagger-ui"' ], 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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>{{packageName}}</groupId> <artifactId>{{serviceName}}</artifactId> <version>0.1</version> <packaging>jar</packaging> <parent> <groupId>io.micronaut.platform</groupId> <artifactId>micronaut-parent</artifactId> <version>4.2.0</version> </parent> <properties> <packaging>jar</packaging> <jdk.version>17</jdk.version> <release.version>17</release.version> <micronaut.version>4.2.0</micronaut.version> <micronaut.runtime>netty</micronaut.runtime> <micronaut.data.version>4.2.0</micronaut.data.version> <exec.mainClass>{{packageName}}.Application</exec.mainClass> </properties> <repositories> <repository> <id>central</id> <url>https://repo.maven.apache.org/maven2</url> </repository> </repositories> <dependencies> <!-- Micronaut Core --> <dependency> <groupId>io.micronaut</groupId> <artifactId>micronaut-inject</artifactId> </dependency> <dependency> <groupId>io.micronaut</groupId> <artifactId>micronaut-runtime</artifactId> </dependency> <dependency> <groupId>io.micronaut</groupId> <artifactId>micronaut-http-server-netty</artifactId> </dependency> <dependency> <groupId>io.micronaut</groupId> <artifactId>micronaut-http-client</artifactId> </dependency> <!-- Validation --> <dependency> <groupId>io.micronaut.validation</groupId> <artifactId>micronaut-validation</artifactId> </dependency> <dependency> <groupId>jakarta.validation</groupId> <artifactId>jakarta.validation-api</artifactId> </dependency> <!-- Security --> <dependency> <groupId>io.micronaut.security</groupId> <artifactId>micronaut-security-jwt</artifactId> </dependency> <dependency> <groupId>io.micronaut.security</groupId> <artifactId>micronaut-security-annotations</artifactId> </dependency> <!-- Data Access --> <dependency> <groupId>io.micronaut.data</groupId> <artifactId>micronaut-data-jdbc</artifactId> </dependency> <dependency> <groupId>io.micronaut.sql</groupId> <artifactId>micronaut-jdbc-hikari</artifactId> </dependency> <dependency> <groupId>io.micronaut.flyway</groupId> <artifactId>micronaut-flyway</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <!-- Cache --> <dependency> <groupId>io.micronaut.cache</groupId> <artifactId>micronaut-cache-caffeine</artifactId> </dependency> <dependency> <groupId>io.micronaut.redis</groupId> <artifactId>micronaut-redis-lettuce</artifactId> </dependency> <!-- OpenAPI --> <dependency> <groupId>io.micronaut.openapi</groupId> <artifactId>micronaut-openapi</artifactId> </dependency> <!-- Monitoring --> <dependency> <groupId>io.micronaut.micrometer</groupId> <artifactId>micronaut-micrometer-core</artifactId> </dependency> <dependency> <groupId>io.micronaut.micrometer</groupId> <artifactId>micronaut-micrometer-registry-prometheus</artifactId> </dependency> <dependency> <groupId>io.micronaut</groupId> <artifactId>micronaut-management</artifactId> </dependency> <!-- AOP --> <dependency> <groupId>io.micronaut</groupId> <artifactId>micronaut-aop</artifactId> </dependency> <!-- Serialization --> <dependency> <groupId>io.micronaut.serde</groupId> <artifactId>micronaut-serde-jackson</artifactId> </dependency> <!-- Logging --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <scope>runtime</scope> </dependency> <!-- Testing --> <dependency> <groupId>io.micronaut.test</groupId> <artifactId>micronaut-test-junit5</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>testcontainers</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>postgresql</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>io.micronaut.maven</groupId> <artifactId>micronaut-maven-plugin</artifactId> <configuration> <configFile>aot-\${packaging}.properties</configFile> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-enforcer-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <annotationProcessorPaths combine.self="override"> <path> <groupId>io.micronaut</groupId> <artifactId>micronaut-inject-java</artifactId> <version>\${micronaut.core.version}</version> </path> <path> <groupId>io.micronaut.data</groupId> <artifactId>micronaut-data-processor</artifactId> <version>\${micronaut.data.version}</version> </path> <path> <groupId>io.micronaut</groupId> <artifactId>micronaut-graal</artifactId> <version>\${micronaut.core.version}</version> </path> <path> <groupId>io.micronaut.openapi</groupId> <artifactId>micronaut-openapi</artifactId> <version>\${micronaut.openapi.version}</version> </path> <path> <groupId>io.micronaut.serde</groupId> <artifactId>micronaut-serde-processor</artifactId> <version>\${micronaut.serialization.version}</version> </path> <path> <groupId>io.micronaut.validation</groupId> <artifactId>micronaut-validation-processor</artifactId> <version>\${micronaut.validation.version}</version> </path> </annotationProcessorPaths> <compilerArgs> <arg>-Amicronaut.processing.group={{packageName}}</arg> <arg>-Amicronaut.processing.module={{serviceName}}</arg> </compilerArgs> </configuration> </plugin> </plugins> </build> </project> `, 'src/main/java/{{packagePath}}/Application.java': `package {{packageName}}; import io.micronaut.runtime.Micronaut; import io.swagger.v3.oas.annotations.*; import io.swagger.v3.oas.annotations.info.*; @OpenAPIDefinition( info = @Info( title = "{{serviceName}}", version = "1.0", description = "{{description}}", contact = @Contact(name = "{{team}}", email = "{{team}}@{{org}}.com") ) ) public class Application { public static void main(String[] args) { Micronaut.run(Application.class, args); } } `, 'src/main/java/{{packagePath}}/entity/User.java': `package {{packageName}}.entity; import io.micronaut.core.annotation.Introspected; import io.micronaut.data.annotation.*; import io.micronaut.serde.annotation.Serdeable; import javax.validation.constraints.*; import java.time.LocalDateTime; import java.util.Set; @Serdeable @Introspected @MappedEntity("users") public class User { @Id @GeneratedValue(GeneratedValue.Type.AUTO) private Long id; @NotBlank @Size(min = 3, max = 50) private String username; @NotBlank @Email private String email; @NotBlank private String password; private String firstName; private String lastName; private Set<String> roles; private boolean enabled = true; @DateCreated private LocalDateTime createdAt; @DateUpdated private LocalDateTime updatedAt; // Getters and setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public Set<String> getRoles() { return roles; } public void setRoles(Set<String> roles) { this.roles = roles; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public LocalDateTime getCreatedAt() { return createdAt; } public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } public LocalDateTime getUpdatedAt() { return updatedAt; } public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } } `, 'src/main/java/{{packagePath}}/repository/UserRepository.java': `package {{packageName}}.repository; import {{packageName}}.entity.User; import io.micronaut.data.jdbc.annotation.JdbcRepository; import io.micronaut.data.model.query.builder.sql.Dialect; import io.micronaut.data.repository.PageableRepository; import java.util.Optional; @JdbcRepository(dialect = Dialect.POSTGRES) public interface UserRepository extends PageableRepository<User, Long> { Optional<User> findByUsername(String username); Optional<User> findByEmail(String email); boolean existsByUsername(String username); boolean existsByEmail(String email); Optional<User> findByUsernameOrEmail(String username, String email); } `, 'src/main/java/{{packagePath}}/service/UserService.java': `package {{packageName}}.service; import {{packageName}}.dto.UserDto; import {{packageName}}.entity.User; import {{packageName}}.exception.ResourceNotFoundException; import {{packageName}}.repository.UserRepository; import io.micronaut.cache.annotation.*; import io.micronaut.data.model.Page; import io.micronaut.data.model.Pageable; import io.micronaut.security.authentication.Authentication; import io.micronaut.transaction.annotation.TransactionalAdvice; import jakarta.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashSet; import java.util.Set; @Singleton @TransactionalAdvice public class UserService { private static final Logger LOG = LoggerFactory.getLogger(UserService.class); private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) { this.userRepository = userRepository; this.passwordEncoder = passwordEncoder; } @Cacheable("user-cache") public UserDto findById(Long id) { User user = userRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id)); return UserDto.fromEntity(user); } public UserDto findByUsername(String username) { User user = userRepository.findByUsername(username) .orElseThrow(() -> new ResourceNotFoundException("User not found with username: " + username)); return UserDto.fromEntity(user); } public Page<UserDto> findAll(Pageable pageable) { return userRepository.findAll(pageable) .map(UserDto::fromEntity); } 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 = new User(); user.setUsername(userDto.getUsername()); user.setEmail(userDto.getEmail()); user.setPassword(passwordEncoder.encode(userDto.getPassword())); user.setFirstName(userDto.getFirstName()); user.setLastName(userDto.getLastName()); Set<String> roles = new HashSet<>(); roles.add("ROLE_USER"); user.setRoles(roles); user = userRepository.save(user); LOG.info("Created new user: {}", user.getUsername()); return UserDto.fromEntity(user); } @CacheInvalidate("user-cache") public UserDto update(Long id, UserDto userDto) { User user = userRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id)); if (userDto.getFirstName() != null) { user.setFirstName(userDto.getFirstName()); } if (userDto.getLastName() != null) { user.setLastName(userDto.getLastName()); } if (userDto.getEmail() != null && !userDto.getEmail().equals(user.getEmail())) { if (userRepository.existsByEmail(userDto.getEmail())) { throw new IllegalArgumentException("Email already exists"); } user.setEmail(userDto.getEmail()); } user = userRepository.update(user); LOG.info("Updated user: {}", user.getUsername()); return UserDto.fromEntity(user); } @CacheInvalidate("user-cache") public void delete(Long id) { User user = userRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id)); userRepository.delete(user); LOG.info("Deleted user: {}", user.getUsername()); } public boolean checkPassword(String username, String password) { User user = userRepository.findByUsername(username) .orElse(null); if (user == null) { user = userRepository.findByEmail(username) .orElse(null); } return user != null && passwordEncoder.matches(password, user.getPassword()); } } `, 'src/main/java/{{packagePath}}/controller/UserController.java': `package {{packageName}}.controller; import {{packageName}}.dto.UserDto; import {{packageName}}.service.UserService; import io.micronaut.data.model.Page; import io.micronaut.data.model.Pageable; import io.micronaut.http.HttpResponse; import io.micronaut.http.annotation.*; import io.micronaut.security.annotation.Secured; import io.micronaut.security.rules.SecurityRule; import io.micronaut.validation.Validated; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import javax.validation.Valid; @Validated @Controller("/api/users") @Tag(name = "User Management", description = "User management endpoints") public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService = userService; } @Get @Secured("ROLE_ADMIN") @Operation(summary = "Get all users", description = "Retrieve a paginated list of users") @ApiResponse(responseCode = "200", description = "List of users", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Page.class))) public HttpResponse<Page<UserDto>> getUsers(Pageable pageable) { return HttpResponse.ok(userService.findAll(pageable)); } @Get("/{id}") @Secured(SecurityRule.IS_AUTHENTICATED) @Operation(summary = "Get user by ID", description = "Retrieve a specific user") @ApiResponse(responseCode = "200", description = "User found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = UserDto.class))) @ApiResponse(responseCode = "404", description = "User not found") public HttpResponse<UserDto> getUser(Long id) { return HttpResponse.ok(userService.findById(id)); } @Post @Secured("ROLE_ADMIN") @Operation(summary = "Create user", description = "Create a new user") @ApiResponse(responseCode = "201", description = "User created", content = @Content(mediaType = "application/json", schema = @Schema(implementation = UserDto.class))) @ApiResponse(responseCode = "400", description = "Invalid input") public HttpResponse<UserDto> createUser(@Body @Valid UserDto userDto) { return HttpResponse.created(userService.create(userDto)); } @Put("/{id}") @Secured(SecurityRule.IS_AUTHENTICATED) @Operation(summary = "Update user", description = "Update an existing user") @ApiResponse(responseCode = "200", description = "User updated", content = @Content(mediaType = "application/json", schema = @Schema(implementation = UserDto.class))) @ApiResponse(responseCode = "404", description = "User not found") public HttpResponse<UserDto> updateUser(Long id, @Body @Valid UserDto userDto) { return HttpResponse.ok(userService.update(id, userDto)); } @Delete("/{id}") @Secured("ROLE_ADMIN") @Operation(summary = "Delete user", description = "Delete a user") @ApiResponse(responseCode = "204", description = "User deleted") @ApiResponse(responseCode = "404", description = "User not found") public HttpResponse<Void> deleteUser(Long id) { userService.delete(id); return HttpResponse.noContent(); } } `, 'src/main/java/{{packagePath}}/controller/AuthController.java': `package {{packageName}}.controller; import {{packageName}}.dto.*; import {{packageName}}.service.AuthService; import io.micronaut.http.HttpResponse; import io.micronaut.http.annotation.*; import io.micronaut.security.annotation.Secured; import io.micronaut.security.rules.SecurityRule; import io.micronaut.validation.Validated; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import javax.validation.Valid; @Validated @Controller("/api/auth") @Secured(SecurityRule.IS_ANONYMOUS) @Tag(name = "Authentication", description = "Authentication endpoints") public class AuthController { private final AuthService authService; public AuthController(AuthService authService) { this.authService = authService; } @Post("/register") @Operation(summary = "Register new user", description = "Register a new user account") @ApiResponse(responseCode = "201", description = "User registered successfully", content = @Content(mediaType = "application/json", schema = @Schema(implementation = AuthResponse.class))) @ApiResponse(responseCode = "400", description = "Invalid input or user already exists") public HttpResponse<AuthResponse> register(@Body @Valid RegisterRequest request) { return HttpResponse.created(authService.register(request)); } @Post("/login") @Operation(summary = "Authenticate user", description = "Authenticate user and receive JWT token") @ApiResponse(responseCode = "200", description = "Authentication successful", content = @Content(mediaType = "application/json", schema = @Schema(implementation = AuthResponse.class))) @ApiResponse(responseCode = "401", description = "Invalid credentials") public HttpResponse<AuthResponse> login(@Body @Valid LoginRequest request) { return HttpResponse.ok(authService.authenticate(request)); } @Post("/refresh") @Operation(summary = "Refresh token", description = "Refresh JWT token using refresh token") @ApiResponse(responseCode = "200", description = "Token refreshed successfully", content = @Content(mediaType = "application/json", schema = @Schema(implementation = AuthResponse.class))) @ApiResponse(responseCode = "401", description = "Invalid refresh token") public HttpResponse<AuthResponse> refresh(@QueryValue String refreshToken) { return HttpResponse.ok(authService.refreshToken(refreshToken)); } } `, 'src/main/java/{{packagePath}}/service/AuthService.java': `package {{packageName}}.service; import {{packageName}}.dto.*; import {{packageName}}.entity.User; import {{packageName}}.security.AuthenticationProviderUserPassword; import io.micronaut.security.authentication.UsernamePasswordCredentials; import io.micronaut.security.token.jwt.generator.JwtTokenGenerator; import io.micronaut.security.token.jwt.render.AccessRefreshToken; import jakarta.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.Map; import java.util.Optional; @Singleton public class AuthService { private static final Logger LOG = LoggerFactory.getLogger(AuthService.class); private final UserService userService; private final JwtTokenGenerator tokenGenerator; private final AuthenticationProviderUserPassword authProvider; public AuthService(UserService userService, JwtTokenGenerator tokenGenerator, AuthenticationProviderUserPassword authProvider) { this.userService = userService; this.tokenGenerator = tokenGenerator; this.authProvider = authProvider; } public AuthResponse register(RegisterRequest request) { UserDto userDto = new UserDto(); userDto.setUsername(request.getUsername()); userDto.setEmail(request.getEmail()); userDto.setPassword(request.getPassword()); userDto.setFirstName(request.getFirstName()); userDto.setLastName(request.getLastName()); UserDto createdUser = userService.create(userDto); // Generate tokens Map<String, Object> claims = new HashMap<>(); claims.put("username", createdUser.getUsername()); claims.put("email", createdUser.getEmail()); claims.put("roles", createdUser.getRoles()); Optional<String> accessToken = tokenGenerator.generateToken(claims); Optional<String> refreshToken = tokenGenerator.generateToken(claims); AuthResponse response = new AuthResponse(); response.setAccessToken(accessToken.orElse("")); response.setRefreshToken(refreshToken.orElse("")); response.setTokenType("Bearer"); response.setExpiresIn(3600L); response.setUser(createdUser); LOG.info("User registered successfully: {}", createdUser.getUsername()); return response; } public AuthResponse authenticate(LoginRequest request) { var credentials = new UsernamePasswordCredentials(request.getUsername(), request.getPassword()); var authResponse = authProvider.authenticate(null, credentials); if (authResponse.isAuthenticated()) { UserDto user = userService.findByUsername(authResponse.getAuthentication().get().getName()); Map<String, Object> claims = new HashMap<>(); claims.put("username", user.getUsername()); claims.put("email", user.getEmail()); claims.put("roles", user.getRoles()); Optional<String> accessToken = tokenGenerator.generateToken(claims); Optional<String> refreshToken = tokenGenerator.generateToken(claims); AuthResponse response = new AuthResponse(); response.setAccessToken(accessToken.orElse("")); response.setRefreshToken(refreshToken.orElse("")); response.setTokenType("Bearer"); response.setExpiresIn(3600L); response.setUser(user); LOG.info("User authenticated successfully: {}", user.getUsername()); return response; } else { throw new IllegalArgumentException("Invalid credentials"); } } public AuthResponse refreshToken(String refreshToken) { // In a real implementation, validate the refresh token // For now, we'll generate new tokens // This is a simplified implementation // You should implement proper refresh token validation throw new UnsupportedOperationException("Refresh token not implemented"); } } `, 'src/main/java/{{packagePath}}/security/AuthenticationProviderUserPassword.java': `package {{packageName}}.security; import {{packageName}}.service.UserService; import io.micronaut.core.annotation.Nullable; import io.micronaut.http.HttpRequest; import io.micronaut.security.authentication.*; import jakarta.inject.Singleton; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; import java.util.Collections; @Singleton public class AuthenticationProviderUserPassword implements AuthenticationProvider { private final UserService userService; public AuthenticationProviderUserPassword(UserService userService) { this.userService = userService; } @Override public Publisher<AuthenticationResponse> authenticate(@Nullable HttpRequest<?> httpRequest, AuthenticationRequest<?, ?> authenticationRequest) { String username = authenticationRequest.getIdentity().toString(); String password = authenticationRequest.getSecret().toString(); return Mono.create(emitter -> { if (userService.checkPassword(username, password)) { var userDto = userService.findByUsername(username); emitter.success(AuthenticationResponse.success( username, userDto.getRoles(), Collections.singletonMap("email", userDto.getEmail()) )); } else { emitter.error(AuthenticationResponse.exception()); } }); } } `, 'src/main/java/{{packagePath}}/security/PasswordEncoder.java': `package {{packageName}}.security; import jakarta.inject.Singleton; import org.mindrot.jbcrypt.BCrypt; @Singleton public class PasswordEncoder { public String encode(String password) { return BCrypt.hashpw(password, BCrypt.gensalt()); } public boolean matches(String password, String encodedPassword) { return BCrypt.checkpw(password, encodedPassword); } } `, 'src/main/java/{{packagePath}}/dto/UserDto.java': `package {{packageName}}.dto; import {{packageName}}.entity.User; import com.fasterxml.jackson.annotation.JsonProperty; import io.micronaut.core.annotation.Introspected; import io.micronaut.serde.annotation.Serdeable; import javax.validation.constraints.*; import java.time.LocalDateTime; import java.util.Set; @Serdeable @Introspected 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; // Static factory method public static UserDto fromEntity(User user) { UserDto dto = new UserDto(); dto.setId(user.getId()); dto.setUsername(user.getUsername()); dto.setEmail(user.getEmail()); dto.setFirstName(user.getFirstName()); dto.setLastName(user.getLastName()); dto.setRoles(user.getRoles()); dto.setEnabled(user.isEnabled()); dto.setCreatedAt(user.getCreatedAt()); dto.setUpdatedAt(user.getUpdatedAt()); return dto; } // Getters and setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public Set<String> getRoles() { return roles; } public void setRoles(Set<String> roles) { this.roles = roles; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public LocalDateTime getCreatedAt() { return createdAt; } public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } public LocalDateTime getUpdatedAt() { return updatedAt; } public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } } `, 'src/main/java/{{packagePath}}/dto/LoginRequest.java': `package {{packageName}}.dto; import io.micronaut.core.annotation.Introspected; import io.micronaut.serde.annotation.Serdeable; import javax.validation.constraints.NotBlank; @Serdeable @Introspected public class LoginRequest { @NotBlank(message = "Username or email is required") private String username; @NotBlank(message = "Password is required") private String password; // Getters and setters public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } `, 'src/main/java/{{packagePath}}/dto/RegisterRequest.java': `package {{packageName}}.dto; import io.micronaut.core.annotation.Introspected; import io.micronaut.serde.annotation.Serdeable; import javax.validation.constraints.*; @Serdeable @Introspected 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; // Getters and setters public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } } `, 'src/main/java/{{packagePath}}/dto/AuthResponse.java': `package {{packageName}}.dto; import com.fasterxml.jackson.annotation.JsonProperty; import io.micronaut.core.annotation.Introspected; import io.micronaut.serde.annotation.Serdeable; @Serdeable @Introspected public class AuthResponse { @JsonProperty("access_token") private String accessToken; @JsonProperty("refresh_token") private String refreshToken; @JsonProperty("token_type") private String tokenType; @JsonProperty("expires_in") private Long expiresIn; private UserDto user; // Getters and setters public String getAccessToken() { return accessToken; } public void setAccessToken(String accessToken) { this.accessToken = accessToken; } public String getRefreshToken() { return refreshToken; } public void setRefreshToken(String refreshToken) { this.refreshToken = refreshToken; } public String getTokenType() { return tokenType; } public void setTokenType(String tokenType) { this.tokenType = tokenType; } public Long getExpiresIn() { return expiresIn; } public void setExpiresIn(Long expiresIn) { this.expiresIn = expiresIn; } public UserDto getUser() { return user; } public void setUser(UserDto user) { this.user = user; } } `, 'src/main/java/{{packagePath}}/exception/ResourceNotFoundException.java': `package {{packageName}}.exception; import io.micronaut.http.HttpStatus; import io.micronaut.http.exceptions.HttpStatusException; public class ResourceNotFoundException extends HttpStatusException { public ResourceNotFoundException(String message) { super(HttpStatus.NOT_FOUND, message); } } `, 'src/main/java/{{packagePath}}/exception/GlobalExceptionHandler.java': `package {{packageName}}.exception; import io.micronaut.context.annotation.Requires; import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; import io.micronaut.http.HttpStatus; import io.micronaut.http.annotation.Produces; import io.micronaut.http.server.exceptions.ExceptionHandler; import io.micronaut.http.server.exceptions.response.ErrorContext; import io.micronaut.http.server.exceptions.response.ErrorResponseProcessor; import jakarta.inject.Singleton; import javax.validation.ConstraintViolationException; import java.util.HashMap; import java.util.Map; @Produces @Singleton @Requires(classes = {ConstraintViolationException.class, ExceptionHandler.class}) public class GlobalExceptionHandler implements ExceptionHandler<Exception, HttpResponse<?>> { private final ErrorResponseProcessor<?> errorResponseProcessor; public GlobalExceptionHandler(ErrorResponseProcessor<?> errorResponseProcessor) { this.errorResponseProcessor = errorResponseProcessor; } @Override public HttpResponse<?> handle(HttpRequest request, Exception exception) { Map<String, Object> error = new HashMap<>(); if (exception instanceof ResourceNotFoundException) { error.put("message", exception.getMessage()); error.put("status", HttpStatus.NOT_FOUND.getCode()); return HttpResponse.notFound(error); } if (exception instanceof IllegalArgumentException) { error.put("message", exception.getMessage()); error.put("status", HttpStatus.BAD_REQUEST.getCode()); return HttpResponse.badRequest(error); } if (exception instanceof ConstraintViolationException) { ConstraintViolationException cve = (ConstraintViolationException) exception; Map<String, String> violations = new HashMap<>(); cve.getConstraintViolations().forEach(violation -> { String path = violation.getPropertyPath().toString(); String message = violation.getMessage(); violations.put(path, message); }); error.put("message", "Validation failed"); error.put("violations", violations); error.put("status", HttpStatus.BAD_REQUEST.getCode()); return HttpResponse.badRequest(error); } error.put("message", "An unexpected error occurred"); error.put("status", HttpStatus.INTERNAL_SERVER_ERROR.getCode()); return HttpResponse.serverError(error); } } `, 'src/main/java/{{packagePath}}/aop/LoggingInterceptor.java': `package {{packageName}}.aop; import io.micronaut.aop.InterceptorBean; import io.micronaut.aop.MethodInterceptor; import io.micronaut.aop.MethodInvocationContext; import jakarta.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Singleton @InterceptorBean(Loggable.class) public class LoggingInterceptor implements MethodInterceptor<Object, Object> { private static final Logger LOG = LoggerFactory.getLogger(LoggingInterceptor.class); @Override public Object intercept(MethodInvocationContext<Object, Object> context) { String methodName = context.getMethodName(); LOG.debug("Executing method: {}", methodName); long startTime = System.currentTimeMillis(); try { Object result = context.proceed(); long duration = System.currentTimeMillis() - startTime; LOG.debug("Method {} executed in {} ms", methodName, duration); return result; } catch (Exception e) { LOG.error("Method {} threw exception: {}", methodName, e.getMessage()); throw e; } } } `, 'src/main/java/{{packagePath}}/aop/Loggable.java': `package {{packageName}}.aop; import io.micronaut.aop.Around; import io.micronaut.context.annotation.Type; import java.lang.annotation.*; @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Around @Type(LoggingInterceptor.class) public @interface Loggable { } `, 'src/main/java/{{packagePath}}/health/DatabaseHealthIndicator.java': `package {{packageName}}.health; import io.micronaut.health.HealthStatus; import io.micronaut.management.health.indicator.HealthIndicator; import io.micronaut.management.health.indicator.HealthResult; import jakarta.inject.Singleton; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; @Singleton public class DatabaseHealthIndicator implements HealthIndicator { private final DataSource dataSource; public DatabaseHealthIndicator(DataSource dataSource) { this.dataSource = dataSource; } @Override public Publisher<HealthResult> getResult() { return Mono.fromCallable(() -> { try (Connection connection = dataSource.getConnection()) { if (connection.isValid(1)) { return HealthResult.builder("database") .status(HealthStatus.UP) .details("Database connection is healthy") .build(); } } catch (SQLException e) { return HealthResult.builder("database") .status(HealthStatus.DOWN) .details("Database connection failed: " + e.getMessage()) .build(); } return HealthResult.builder("database") .status(HealthStatus.DOWN) .details("Database connection is not valid") .build(); }); } } `, 'src/main/resources/application.yml': `micronaut: application: name: {{serviceName}} server: port: {{port}} cors: enabled: true configurations: web: allowedOrigins: - http://localhost:3000 - http://localhost:5173 allowedMethods: - GET - POST - PUT - DELETE - OPTIONS allowedHeaders: - "*" exposedHeaders: - Authorization security: enabled: true endpoints: login: enabled: true logout: enabled: true intercept-url-map: - pattern: /swagger/** access: - isAnonymous() - pattern: /swagger-ui/** access: - isAnonymous() - pattern: /api/auth/** access: - isAnonymous() authentication: bearer token: jwt: signatures: secret: generator: secret: "\${JWT_SECRET:pleaseChangeThisSecretForANewOne}" jws-algorithm: HS256 generator: refresh-token: secret: "\${JWT_REFRESH_SECRET:pleaseChangeThisRefreshSecretForANewOne}" access-token: expiration: 3600 router: static-resources: swagger: paths: classpath:META-INF/swagger mapping: /swagger/** swagger-ui: paths: classpath:META-INF/swagger/views/swagger-ui mapping: /swagger-ui/** datasources: default: url: jdbc:postgresql://\${DB_HOST:localhost}:\${DB_PORT:5432}/\${DB_NAME:{{serviceName}}_db} driverClassName: org.postgresql.Driver username: \${DB_USERNAME:postgres} password: \${DB_PASSWORD:postgres} schema-generate: NONE dialect: POSTGRES flyway: datasources: default: enabled: true locations: - classpath:db/migration redis: uri: redis://\${REDIS_HOST:localhost}:\${REDIS_PORT:6379} cache: user-cache: expire-after-write: 10m maximum-size: 1000 jackson: serialization: write-dates-as-timestamps: false deserialization: fail-on-unknown-properties: false endpoints: all: path: /management sensitive: false health: enabled: true sensitive: false details-visible: ANONYMOUS metrics: enabled: true sensitive: false prometheus: enabled: true sensitive: false micronaut.metrics: enabled: true export: prometheus: enabled: true step: PT1M descriptions: true `, 'src/main/resources/db/migration/V1__Create_users_table.sql': `CREATE TABLE users ( id BIGSERIAL PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, email VARCHAR(100) UNIQUE NOT NULL, password VARCHAR(255) NOT NULL, first_name VARCHAR(50), last_name VARCHAR(50), roles TEXT, enabled BOOLEAN DEFAULT true, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP ); CREATE INDEX idx_users_username ON users(username); CREATE INDEX idx_users_email ON users(email); CREATE INDEX idx_users_enabled ON users(enabled); -- Create admin user (password: admin123) INSERT INTO users (username, email, password, first_name, last_name, roles) VALUES ('admin', 'admin@{{org}}.com', '$2a$10$mVWJ3N5pJ.6KJxPl8XhKaOjFqQH.Y3bg8gFRT6bCBLrJWBZwJvj9S', 'Admin', 'User', 'ROLE_ADMIN,ROLE_USER'); `, 'src/main/resources/logback.xml': `<configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <withJansi>true</withJansi> <encoder> <pattern>%cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n</pattern> </encoder> </appender> <root level="info"> <appender-ref ref="STDOUT" /> </root> <logger name="{{packageName}}" level="debug" /> <logger name="io.micronaut.http.server" level="debug" /> <logger name="io.micronaut.data.query" level="debug" /> </configuration> `, 'src/test/java/{{packagePath}}/UserControllerTest.java': `package {{packageName}}; import {{packageName}}.dto.UserDto; import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpStatus; import io.micronaut.http.client.HttpClient; import io.micronaut.http.client.annotation.Client; import io.micronaut.http.client.exceptions.HttpClientResponseException; import io.micronaut.security.authentication.UsernamePasswordCredentials; import io.micronaut.security.token.jwt.render.BearerAccessRefreshToken; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import jakarta.inject.Inject; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @MicronautTest public class UserControllerTest { @Inject @Client("/") HttpClient client; @Test void testGetUsersRequiresAuthentication() { HttpClientResponseException thrown = assertThrows( HttpClientResponseException.class, () -> client.toBlocking().exchange("/api/users") ); assertEquals(HttpStatus.UNAUTHORIZED, thrown.getStatus()); } @Test void testGetUsersWithAdminRole() { // First authenticate as admin UsernamePasswordCredentials creds = new UsernamePasswordCredentials("admin", "admin123"); HttpRequest<?> request = HttpRequest.POST("/login", creds); BearerAccessRefreshToken token = client.toBlocking().retrieve(request, BearerAccessRefreshToken.class); // Then access users endpoint HttpRequest<?> usersRequest = HttpRequest.GET("/api/users") .bearerAuth(token.getAccessToken()); String response = client.toBlocking().retrieve(usersRequest); assertNotNull(response); } } `, 'src/test/java/{{packagePath}}/AuthControllerTest.java': `package {{packageName}}; import {{packageName}}.dto.*; import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpStatus; import io.micronaut.http.client.HttpClient; import io.micronaut.http.client.annotation.Client; import io.micronaut.http.client.exceptions.HttpClientResponseException; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import jakarta.inject.Inject; import org.junit.jupiter.api.Test; import java.util.UUID; import static org.junit.jupiter.api.Assertions.*; @MicronautTest public class AuthControllerTest { @Inject @Client("/") HttpClient client; @Test void testRegisterNewUser() { RegisterRequest request = new RegisterRequest(); request.setUsername("testuser_" + UUID.randomUUID()); request.setEmail("test_" + UUID.randomUUID() + "@example.com"); request.setPassword("password123"); request.setFirstName("Test"); request.setLastName("User"); HttpRequest<Re