@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,335 lines (1,149 loc) • 58.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.SpringBootGenerator = void 0;
const kotlin_base_generator_1 = require("./kotlin-base-generator");
const fs_1 = require("fs");
const path = __importStar(require("path"));
class SpringBootGenerator extends kotlin_base_generator_1.KotlinBackendGenerator {
constructor() {
super('Spring Boot');
}
getFrameworkPlugins() {
return `id("org.springframework.boot") version "3.2.0"
id("io.spring.dependency-management") version "1.1.4"
kotlin("plugin.spring") version "1.9.20"
kotlin("plugin.jpa") version "1.9.20"`;
}
getFrameworkDependencies() {
return `implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-cache")
implementation("org.springframework.boot:spring-boot-starter-websocket")
implementation("org.springframework.boot:spring-boot-starter-data-redis")
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0")
implementation("org.springdoc:springdoc-openapi-starter-kotlin:2.3.0")
implementation("org.springframework.kafka:spring-kafka")
implementation("org.liquibase:liquibase-core")
implementation("org.springframework.cloud:spring-cloud-starter-sleuth:3.1.9")
implementation("org.springframework.boot:spring-boot-starter-graphql")
implementation("com.graphql-java:graphql-java-extended-scalars:21.0")
developmentOnly("org.springframework.boot:spring-boot-devtools")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")
testImplementation("org.springframework.boot:spring-boot-testcontainers")
testImplementation("org.springframework.restdocs:spring-restdocs-mockmvc")
testImplementation("com.ninja-squad:springmockk:4.0.2")`;
}
getFrameworkTasks() {
return `springBoot {
buildInfo()
}
tasks.bootJar {
enabled = true
archiveFileName.set("app.jar")
}
tasks.jar {
enabled = false
}`;
}
async generateFrameworkFiles(projectPath, options) {
const basePackage = options.organization || 'com.example';
const srcDir = path.join(projectPath, 'src/main/kotlin', ...basePackage.split('.'));
await fs_1.promises.mkdir(srcDir, { recursive: true });
await this.generateApplication(srcDir, basePackage, options);
await this.generateConfiguration(srcDir, basePackage);
await this.generateControllers(srcDir, basePackage);
await this.generateServices(srcDir, basePackage);
await this.generateRepositories(srcDir, basePackage);
await this.generateEntities(srcDir, basePackage);
await this.generateDtos(srcDir, basePackage);
await this.generateSecurity(srcDir, basePackage);
await this.generateExceptions(srcDir, basePackage);
await this.generateWebSocket(srcDir, basePackage);
await this.generateGraphQL(srcDir, basePackage);
await this.generateUtils(srcDir, basePackage);
await this.generateResources(projectPath);
await this.generateTests(projectPath, basePackage);
}
async generateApplication(srcDir, basePackage, options) {
const appContent = `package ${basePackage}
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.cache.annotation.EnableCaching
import org.springframework.scheduling.annotation.EnableAsync
import org.springframework.scheduling.annotation.EnableScheduling
@SpringBootApplication
@EnableCaching
@EnableAsync
@EnableScheduling
class ${options.name.charAt(0).toUpperCase() + options.name.slice(1)}Application
fun main(args: Array<String>) {
runApplication<${options.name.charAt(0).toUpperCase() + options.name.slice(1)}Application>(*args)
}`;
await fs_1.promises.writeFile(path.join(srcDir, `${options.name.charAt(0).toUpperCase() + options.name.slice(1)}Application.kt`), appContent);
}
async generateConfiguration(srcDir, basePackage) {
const configDir = path.join(srcDir, 'config');
await fs_1.promises.mkdir(configDir, { recursive: true });
const webConfigContent = `package ${basePackage}.config
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.CorsRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.web.cors.CorsConfiguration
import org.springframework.web.cors.CorsConfigurationSource
import org.springframework.web.cors.UrlBasedCorsConfigurationSource
@Configuration
class WebConfig : WebMvcConfigurer {
@Value("\\\${app.cors.allowed-origins}")
private lateinit var allowedOrigins: List<String>
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/api/**")
.allowedOrigins(*allowedOrigins.toTypedArray())
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600)
}
@Bean
fun corsConfigurationSource(): CorsConfigurationSource {
val configuration = CorsConfiguration()
configuration.allowedOrigins = allowedOrigins
configuration.allowedMethods = listOf("*")
configuration.allowedHeaders = listOf("*")
configuration.allowCredentials = true
val source = UrlBasedCorsConfigurationSource()
source.registerCorsConfiguration("/**", configuration)
return source
}
}`;
await fs_1.promises.writeFile(path.join(configDir, 'WebConfig.kt'), webConfigContent);
const cacheConfigContent = `package ${basePackage}.config
import org.springframework.cache.CacheManager
import org.springframework.cache.concurrent.ConcurrentMapCacheManager
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.cache.RedisCacheConfiguration
import org.springframework.data.redis.cache.RedisCacheManager
import org.springframework.data.redis.connection.RedisConnectionFactory
import java.time.Duration
@Configuration
class CacheConfig {
@Bean
fun cacheManager(redisConnectionFactory: RedisConnectionFactory): CacheManager {
val defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.disableCachingNullValues()
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(defaultConfig)
.transactionAware()
.build()
}
}`;
await fs_1.promises.writeFile(path.join(configDir, 'CacheConfig.kt'), cacheConfigContent);
const asyncConfigContent = `package ${basePackage}.config
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.scheduling.annotation.AsyncConfigurer
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
import java.util.concurrent.Executor
@Configuration
class AsyncConfig : AsyncConfigurer {
@Bean(name = ["taskExecutor"])
override fun getAsyncExecutor(): Executor {
val executor = ThreadPoolTaskExecutor()
executor.corePoolSize = 2
executor.maxPoolSize = 10
executor.queueCapacity = 500
executor.setThreadNamePrefix("Async-")
executor.initialize()
return executor
}
}`;
await fs_1.promises.writeFile(path.join(configDir, 'AsyncConfig.kt'), asyncConfigContent);
const openApiConfigContent = `package ${basePackage}.config
import io.swagger.v3.oas.models.Components
import io.swagger.v3.oas.models.OpenAPI
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
class OpenApiConfig {
@Bean
fun customOpenAPI(): OpenAPI {
return OpenAPI()
.info(
Info()
.title("Spring Boot API")
.version("1.0.0")
.description("Spring Boot backend API documentation")
.license(License().name("Apache 2.0").url("http://springdoc.org"))
)
.components(
Components()
.addSecuritySchemes(
"bearer-key",
SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
)
)
.addSecurityItem(SecurityRequirement().addList("bearer-key"))
}
}`;
await fs_1.promises.writeFile(path.join(configDir, 'OpenApiConfig.kt'), openApiConfigContent);
}
async generateControllers(srcDir, basePackage) {
const controllerDir = path.join(srcDir, 'controller');
await fs_1.promises.mkdir(controllerDir, { recursive: true });
const userControllerContent = `package ${basePackage}.controller
import ${basePackage}.dto.*
import ${basePackage}.service.UserService
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.security.SecurityRequirement
import io.swagger.v3.oas.annotations.tags.Tag
import jakarta.validation.Valid
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
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/v1/users")
@Tag(name = "Users", description = "User management APIs")
@SecurityRequirement(name = "bearer-key")
class UserController(private val userService: UserService) {
@GetMapping
@Operation(summary = "Get all users")
@PreAuthorize("hasRole('ADMIN')")
fun getAllUsers(pageable: Pageable): ResponseEntity<Page<UserDto>> {
return ResponseEntity.ok(userService.findAll(pageable))
}
@GetMapping("/{id}")
@Operation(summary = "Get user by ID")
@PreAuthorize("hasRole('USER')")
fun getUserById(@PathVariable id: Long): ResponseEntity<UserDto> {
return userService.findById(id)
?.let { ResponseEntity.ok(it) }
?: ResponseEntity.notFound().build()
}
@GetMapping("/me")
@Operation(summary = "Get current user")
@PreAuthorize("hasRole('USER')")
fun getCurrentUser(): ResponseEntity<UserDto> {
return ResponseEntity.ok(userService.getCurrentUser())
}
@PutMapping("/{id}")
@Operation(summary = "Update user")
@PreAuthorize("hasRole('USER') and #id == authentication.principal.id or hasRole('ADMIN')")
fun updateUser(
@PathVariable id: Long,
@Valid @RequestBody updateUserDto: UpdateUserDto
): ResponseEntity<UserDto> {
return userService.update(id, updateUserDto)
?.let { ResponseEntity.ok(it) }
?: ResponseEntity.notFound().build()
}
@DeleteMapping("/{id}")
@Operation(summary = "Delete user")
@PreAuthorize("hasRole('ADMIN')")
@ResponseStatus(HttpStatus.NO_CONTENT)
fun deleteUser(@PathVariable id: Long) {
userService.delete(id)
}
}`;
await fs_1.promises.writeFile(path.join(controllerDir, 'UserController.kt'), userControllerContent);
const authControllerContent = `package ${basePackage}.controller
import ${basePackage}.dto.*
import ${basePackage}.service.AuthService
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import jakarta.validation.Valid
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/api/v1/auth")
@Tag(name = "Authentication", description = "Authentication APIs")
class AuthController(private val authService: AuthService) {
@PostMapping("/register")
@Operation(summary = "Register new user")
fun register(@Valid @RequestBody registerDto: RegisterDto): ResponseEntity<TokenDto> {
return ResponseEntity
.status(HttpStatus.CREATED)
.body(authService.register(registerDto))
}
@PostMapping("/login")
@Operation(summary = "Login user")
fun login(@Valid @RequestBody loginDto: LoginDto): ResponseEntity<TokenDto> {
return ResponseEntity.ok(authService.login(loginDto))
}
@PostMapping("/refresh")
@Operation(summary = "Refresh access token")
fun refresh(@Valid @RequestBody refreshTokenDto: RefreshTokenDto): ResponseEntity<TokenDto> {
return ResponseEntity.ok(authService.refresh(refreshTokenDto))
}
@PostMapping("/logout")
@Operation(summary = "Logout user")
@ResponseStatus(HttpStatus.NO_CONTENT)
fun logout(@RequestHeader("Authorization") token: String) {
authService.logout(token)
}
}`;
await fs_1.promises.writeFile(path.join(controllerDir, 'AuthController.kt'), authControllerContent);
const healthControllerContent = `package ${basePackage}.controller
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.boot.actuate.health.Health
import org.springframework.boot.actuate.health.HealthIndicator
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/api/v1/health")
@Tag(name = "Health", description = "Health check APIs")
class HealthController : HealthIndicator {
@GetMapping
@Operation(summary = "Health check")
fun healthCheck(): Map<String, Any> {
return mapOf(
"status" to "UP",
"timestamp" to System.currentTimeMillis(),
"service" to "spring-boot-api"
)
}
override fun health(): Health {
return Health.up()
.withDetail("service", "spring-boot-api")
.build()
}
}`;
await fs_1.promises.writeFile(path.join(controllerDir, 'HealthController.kt'), healthControllerContent);
}
async generateServices(srcDir, basePackage) {
const serviceDir = path.join(srcDir, 'service');
await fs_1.promises.mkdir(serviceDir, { recursive: true });
const userServiceContent = `package ${basePackage}.service
import ${basePackage}.dto.*
import ${basePackage}.entity.User
import ${basePackage}.repository.UserRepository
import ${basePackage}.security.SecurityUtils
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.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@Service
@Transactional
class UserService(
private val userRepository: UserRepository,
private val securityUtils: SecurityUtils
) {
@Cacheable("users")
@Transactional(readOnly = true)
fun findAll(pageable: Pageable): Page<UserDto> {
return userRepository.findAll(pageable).map { it.toDto() }
}
@Cacheable("users", key = "#id")
@Transactional(readOnly = true)
fun findById(id: Long): UserDto? {
return userRepository.findById(id).orElse(null)?.toDto()
}
@Transactional(readOnly = true)
fun findByEmail(email: String): User? {
return userRepository.findByEmail(email)
}
@Transactional(readOnly = true)
fun getCurrentUser(): UserDto {
val email = securityUtils.getCurrentUserEmail()
return findByEmail(email)?.toDto()
?: throw IllegalStateException("Current user not found")
}
@CacheEvict("users", key = "#id")
fun update(id: Long, updateUserDto: UpdateUserDto): UserDto? {
return userRepository.findById(id).orElse(null)?.let { user ->
updateUserDto.name?.let { user.name = it }
updateUserDto.email?.let { user.email = it }
userRepository.save(user).toDto()
}
}
@CacheEvict("users", key = "#id")
fun delete(id: Long) {
userRepository.deleteById(id)
}
private fun User.toDto(): UserDto = UserDto(
id = this.id!!,
email = this.email,
name = this.name,
roles = this.roles.map { it.name },
createdAt = this.createdAt,
updatedAt = this.updatedAt
)
}`;
await fs_1.promises.writeFile(path.join(serviceDir, 'UserService.kt'), userServiceContent);
const authServiceContent = `package ${basePackage}.service
import ${basePackage}.dto.*
import ${basePackage}.entity.User
import ${basePackage}.entity.Role
import ${basePackage}.repository.UserRepository
import ${basePackage}.repository.RoleRepository
import ${basePackage}.security.JwtTokenProvider
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime
@Service
@Transactional
class AuthService(
private val userRepository: UserRepository,
private val roleRepository: RoleRepository,
private val passwordEncoder: PasswordEncoder,
private val jwtTokenProvider: JwtTokenProvider,
private val authenticationManager: AuthenticationManager
) {
fun register(registerDto: RegisterDto): TokenDto {
if (userRepository.existsByEmail(registerDto.email)) {
throw IllegalArgumentException("Email already registered")
}
val userRole = roleRepository.findByName("ROLE_USER")
?: throw IllegalStateException("Default role not found")
val user = User(
email = registerDto.email,
password = passwordEncoder.encode(registerDto.password),
name = registerDto.name,
roles = mutableSetOf(userRole),
isEnabled = true,
createdAt = LocalDateTime.now(),
updatedAt = LocalDateTime.now()
)
userRepository.save(user)
return generateTokens(user)
}
fun login(loginDto: LoginDto): TokenDto {
authenticationManager.authenticate(
UsernamePasswordAuthenticationToken(loginDto.email, loginDto.password)
)
val user = userRepository.findByEmail(loginDto.email)
?: throw IllegalArgumentException("Invalid credentials")
return generateTokens(user)
}
fun refresh(refreshTokenDto: RefreshTokenDto): TokenDto {
val email = jwtTokenProvider.validateTokenAndGetEmail(refreshTokenDto.refreshToken)
val user = userRepository.findByEmail(email)
?: throw IllegalArgumentException("User not found")
return generateTokens(user)
}
fun logout(token: String) {
// Add token to blacklist or invalidate in Redis
jwtTokenProvider.invalidateToken(token)
}
private fun generateTokens(user: User): TokenDto {
val accessToken = jwtTokenProvider.createAccessToken(user.email, user.roles.map { it.name })
val refreshToken = jwtTokenProvider.createRefreshToken(user.email)
return TokenDto(
accessToken = accessToken,
refreshToken = refreshToken,
tokenType = "Bearer",
expiresIn = jwtTokenProvider.accessTokenValidityInMilliseconds / 1000
)
}
}`;
await fs_1.promises.writeFile(path.join(serviceDir, 'AuthService.kt'), authServiceContent);
}
async generateRepositories(srcDir, basePackage) {
const repoDir = path.join(srcDir, 'repository');
await fs_1.promises.mkdir(repoDir, { recursive: true });
const userRepoContent = `package ${basePackage}.repository
import ${basePackage}.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
interface UserRepository : JpaRepository<User, Long> {
fun findByEmail(email: String): User?
fun existsByEmail(email: String): Boolean
@Query("SELECT u FROM User u LEFT JOIN FETCH u.roles WHERE u.email = :email")
fun findByEmailWithRoles(email: String): Optional<User>
@Query("SELECT u FROM User u WHERE u.isEnabled = true")
fun findAllActive(): List<User>
}`;
await fs_1.promises.writeFile(path.join(repoDir, 'UserRepository.kt'), userRepoContent);
const roleRepoContent = `package ${basePackage}.repository
import ${basePackage}.entity.Role
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
@Repository
interface RoleRepository : JpaRepository<Role, Long> {
fun findByName(name: String): Role?
}`;
await fs_1.promises.writeFile(path.join(repoDir, 'RoleRepository.kt'), roleRepoContent);
}
async generateEntities(srcDir, basePackage) {
const entityDir = path.join(srcDir, 'entity');
await fs_1.promises.mkdir(entityDir, { recursive: true });
const userEntityContent = `package ${basePackage}.entity
import jakarta.persistence.*
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.UserDetails
import java.time.LocalDateTime
@Entity
@Table(name = "users")
class User(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
@Column(unique = true, nullable = false)
var email: String,
@Column(nullable = false)
private var password: String,
@Column(nullable = false)
var name: String,
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_roles",
joinColumns = [JoinColumn(name = "user_id")],
inverseJoinColumns = [JoinColumn(name = "role_id")]
)
var roles: MutableSet<Role> = mutableSetOf(),
@Column(nullable = false)
var isEnabled: Boolean = true,
@Column(nullable = false)
var createdAt: LocalDateTime = LocalDateTime.now(),
@Column(nullable = false)
var updatedAt: LocalDateTime = LocalDateTime.now()
) : UserDetails {
override fun getAuthorities(): Collection<GrantedAuthority> {
return roles.map { SimpleGrantedAuthority(it.name) }
}
override fun getPassword(): String = password
fun setPassword(password: String) {
this.password = password
}
override fun getUsername(): String = email
override fun isAccountNonExpired(): Boolean = true
override fun isAccountNonLocked(): Boolean = true
override fun isCredentialsNonExpired(): Boolean = true
override fun isEnabled(): Boolean = isEnabled
@PreUpdate
fun preUpdate() {
updatedAt = LocalDateTime.now()
}
}`;
await fs_1.promises.writeFile(path.join(entityDir, 'User.kt'), userEntityContent);
const roleEntityContent = `package ${basePackage}.entity
import jakarta.persistence.*
@Entity
@Table(name = "roles")
class Role(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
@Column(unique = true, nullable = false)
val name: String,
@Column
val description: String? = null
)`;
await fs_1.promises.writeFile(path.join(entityDir, 'Role.kt'), roleEntityContent);
}
async generateDtos(srcDir, basePackage) {
const dtoDir = path.join(srcDir, 'dto');
await fs_1.promises.mkdir(dtoDir, { recursive: true });
const userDtoContent = `package ${basePackage}.dto
import com.fasterxml.jackson.annotation.JsonFormat
import java.time.LocalDateTime
data class UserDto(
val id: Long,
val email: String,
val name: String,
val roles: List<String>,
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
val createdAt: LocalDateTime,
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
val updatedAt: LocalDateTime
)
data class CreateUserDto(
val email: String,
val password: String,
val name: String
)
data class UpdateUserDto(
val name: String? = null,
val email: String? = null
)`;
await fs_1.promises.writeFile(path.join(dtoDir, 'UserDto.kt'), userDtoContent);
const authDtoContent = `package ${basePackage}.dto
import jakarta.validation.constraints.Email
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Size
data class RegisterDto(
@field:NotBlank(message = "Email is required")
@field:Email(message = "Invalid email format")
val email: String,
@field:NotBlank(message = "Password is required")
@field:Size(min = 8, message = "Password must be at least 8 characters")
val password: String,
@field:NotBlank(message = "Name is required")
val name: String
)
data class LoginDto(
@field:NotBlank(message = "Email is required")
@field:Email(message = "Invalid email format")
val email: String,
@field:NotBlank(message = "Password is required")
val password: String
)
data class TokenDto(
val accessToken: String,
val refreshToken: String,
val tokenType: String = "Bearer",
val expiresIn: Long
)
data class RefreshTokenDto(
@field:NotBlank(message = "Refresh token is required")
val refreshToken: String
)`;
await fs_1.promises.writeFile(path.join(dtoDir, 'AuthDto.kt'), authDtoContent);
const errorDtoContent = `package ${basePackage}.dto
import com.fasterxml.jackson.annotation.JsonFormat
import java.time.LocalDateTime
data class ErrorDto(
val status: Int,
val error: String,
val message: String?,
val path: String,
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
val timestamp: LocalDateTime = LocalDateTime.now()
)
data class ValidationErrorDto(
val field: String,
val message: String
)
data class ApiResponse<T>(
val success: Boolean,
val data: T? = null,
val error: String? = null,
val timestamp: Long = System.currentTimeMillis()
)`;
await fs_1.promises.writeFile(path.join(dtoDir, 'ErrorDto.kt'), errorDtoContent);
}
async generateSecurity(srcDir, basePackage) {
const securityDir = path.join(srcDir, 'security');
await fs_1.promises.mkdir(securityDir, { recursive: true });
const securityConfigContent = `package ${basePackage}.security
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.authentication.AuthenticationManager
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.http.SessionCreationPolicy
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.CorsConfigurationSource
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
class SecurityConfig(
private val jwtAuthenticationEntryPoint: JwtAuthenticationEntryPoint,
private val jwtAuthenticationFilter: JwtAuthenticationFilter,
private val corsConfigurationSource: CorsConfigurationSource
) {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
return http
.cors { it.configurationSource(corsConfigurationSource) }
.csrf { it.disable() }
.exceptionHandling { it.authenticationEntryPoint(jwtAuthenticationEntryPoint) }
.sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
.authorizeHttpRequests { auth ->
auth.requestMatchers(
"/api/v1/auth/**",
"/api/v1/health/**",
"/actuator/**",
"/swagger-ui/**",
"/v3/api-docs/**",
"/ws/**"
).permitAll()
.anyRequest().authenticated()
}
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)
.build()
}
@Bean
fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()
@Bean
fun authenticationManager(config: AuthenticationConfiguration): AuthenticationManager {
return config.authenticationManager
}
}`;
await fs_1.promises.writeFile(path.join(securityDir, 'SecurityConfig.kt'), securityConfigContent);
const jwtTokenProviderContent = `package ${basePackage}.security
import io.jsonwebtoken.*
import io.jsonwebtoken.security.Keys
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import java.util.*
import javax.crypto.SecretKey
@Component
class JwtTokenProvider {
@Value("\\\${app.jwt.secret}")
private lateinit var jwtSecret: String
@Value("\\\${app.jwt.access-token-expiration}")
val accessTokenValidityInMilliseconds: Long = 3600000 // 1 hour
@Value("\\\${app.jwt.refresh-token-expiration}")
private val refreshTokenValidityInMilliseconds: Long = 2592000000 // 30 days
private val key: SecretKey by lazy {
Keys.hmacShaKeyFor(jwtSecret.toByteArray())
}
fun createAccessToken(email: String, roles: List<String>): String {
val now = Date()
val validity = Date(now.time + accessTokenValidityInMilliseconds)
return Jwts.builder()
.setSubject(email)
.claim("roles", roles)
.setIssuedAt(now)
.setExpiration(validity)
.signWith(key)
.compact()
}
fun createRefreshToken(email: String): String {
val now = Date()
val validity = Date(now.time + refreshTokenValidityInMilliseconds)
return Jwts.builder()
.setSubject(email)
.setIssuedAt(now)
.setExpiration(validity)
.signWith(key)
.compact()
}
fun validateTokenAndGetEmail(token: String): String {
return try {
val claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.body
claims.subject
} catch (e: JwtException) {
throw IllegalArgumentException("Invalid JWT token", e)
}
}
fun invalidateToken(token: String) {
// Add to Redis blacklist
}
}`;
await fs_1.promises.writeFile(path.join(securityDir, 'JwtTokenProvider.kt'), jwtTokenProviderContent);
const jwtAuthFilterContent = `package ${basePackage}.security
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.context.SecurityContextHolder
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
@Component
class JwtAuthenticationFilter(
private val jwtTokenProvider: JwtTokenProvider,
private val userDetailsService: UserDetailsService
) : OncePerRequestFilter() {
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val token = getTokenFromRequest(request)
if (token != null) {
try {
val email = jwtTokenProvider.validateTokenAndGetEmail(token)
val userDetails = userDetailsService.loadUserByUsername(email)
val authentication = UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.authorities
)
authentication.details = WebAuthenticationDetailsSource().buildDetails(request)
SecurityContextHolder.getContext().authentication = authentication
} catch (e: Exception) {
logger.error("Cannot set user authentication", e)
}
}
filterChain.doFilter(request, response)
}
private fun getTokenFromRequest(request: HttpServletRequest): String? {
val bearerToken = request.getHeader("Authorization")
return if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
bearerToken.substring(7)
} else null
}
}`;
await fs_1.promises.writeFile(path.join(securityDir, 'JwtAuthenticationFilter.kt'), jwtAuthFilterContent);
const jwtAuthEntryPointContent = `package ${basePackage}.security
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.core.AuthenticationException
import org.springframework.security.web.AuthenticationEntryPoint
import org.springframework.stereotype.Component
@Component
class JwtAuthenticationEntryPoint : AuthenticationEntryPoint {
override fun commence(
request: HttpServletRequest,
response: HttpServletResponse,
authException: AuthenticationException
) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized")
}
}`;
await fs_1.promises.writeFile(path.join(securityDir, 'JwtAuthenticationEntryPoint.kt'), jwtAuthEntryPointContent);
const userDetailsServiceContent = `package ${basePackage}.security
import ${basePackage}.repository.UserRepository
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@Service
class CustomUserDetailsService(
private val userRepository: UserRepository
) : UserDetailsService {
@Transactional(readOnly = true)
override fun loadUserByUsername(username: String): UserDetails {
return userRepository.findByEmailWithRoles(username).orElseThrow {
UsernameNotFoundException("User not found with email: $username")
}
}
}`;
await fs_1.promises.writeFile(path.join(securityDir, 'CustomUserDetailsService.kt'), userDetailsServiceContent);
const securityUtilsContent = `package ${basePackage}.security
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.stereotype.Component
@Component
class SecurityUtils {
fun getCurrentUserEmail(): String {
val authentication = SecurityContextHolder.getContext().authentication
return when (val principal = authentication?.principal) {
is UserDetails -> principal.username
is String -> principal
else -> throw IllegalStateException("User not authenticated")
}
}
fun isAuthenticated(): Boolean {
val authentication = SecurityContextHolder.getContext().authentication
return authentication != null && authentication.isAuthenticated
}
fun hasRole(role: String): Boolean {
val authentication = SecurityContextHolder.getContext().authentication
return authentication?.authorities?.any { it.authority == role } ?: false
}
}`;
await fs_1.promises.writeFile(path.join(securityDir, 'SecurityUtils.kt'), securityUtilsContent);
}
async generateExceptions(srcDir, basePackage) {
const exceptionDir = path.join(srcDir, 'exception');
await fs_1.promises.mkdir(exceptionDir, { recursive: true });
const globalExceptionHandlerContent = `package ${basePackage}.exception
import ${basePackage}.dto.ErrorDto
import ${basePackage}.dto.ValidationErrorDto
import jakarta.servlet.http.HttpServletRequest
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
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 java.time.LocalDateTime
@RestControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException::class)
fun handleValidationExceptions(
ex: MethodArgumentNotValidException,
request: HttpServletRequest
): ResponseEntity<ErrorDto> {
val errors = ex.bindingResult.allErrors.map { error ->
val fieldName = (error as FieldError).field
val errorMessage = error.defaultMessage ?: "Validation failed"
ValidationErrorDto(fieldName, errorMessage)
}
val errorDto = ErrorDto(
status = HttpStatus.BAD_REQUEST.value(),
error = "Validation Failed",
message = errors.joinToString(", ") { "\${it.field}: \${it.message}" },
path = request.requestURI
)
return ResponseEntity.badRequest().body(errorDto)
}
@ExceptionHandler(ResourceNotFoundException::class)
fun handleResourceNotFoundException(
ex: ResourceNotFoundException,
request: HttpServletRequest
): ResponseEntity<ErrorDto> {
val errorDto = ErrorDto(
status = HttpStatus.NOT_FOUND.value(),
error = "Not Found",
message = ex.message,
path = request.requestURI
)
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorDto)
}
@ExceptionHandler(BadCredentialsException::class)
fun handleBadCredentialsException(
ex: BadCredentialsException,
request: HttpServletRequest
): ResponseEntity<ErrorDto> {
val errorDto = ErrorDto(
status = HttpStatus.UNAUTHORIZED.value(),
error = "Unauthorized",
message = "Invalid credentials",
path = request.requestURI
)
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorDto)
}
@ExceptionHandler(AuthenticationException::class)
fun handleAuthenticationException(
ex: AuthenticationException,
request: HttpServletRequest
): ResponseEntity<ErrorDto> {
val errorDto = ErrorDto(
status = HttpStatus.UNAUTHORIZED.value(),
error = "Unauthorized",
message = ex.message,
path = request.requestURI
)
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorDto)
}
@ExceptionHandler(IllegalArgumentException::class)
fun handleIllegalArgumentException(
ex: IllegalArgumentException,
request: HttpServletRequest
): ResponseEntity<ErrorDto> {
val errorDto = ErrorDto(
status = HttpStatus.BAD_REQUEST.value(),
error = "Bad Request",
message = ex.message,
path = request.requestURI
)
return ResponseEntity.badRequest().body(errorDto)
}
@ExceptionHandler(Exception::class)
fun handleGlobalException(
ex: Exception,
request: HttpServletRequest
): ResponseEntity<ErrorDto> {
val errorDto = ErrorDto(
status = HttpStatus.INTERNAL_SERVER_ERROR.value(),
error = "Internal Server Error",
message = "An unexpected error occurred",
path = request.requestURI
)
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorDto)
}
}`;
await fs_1.promises.writeFile(path.join(exceptionDir, 'GlobalExceptionHandler.kt'), globalExceptionHandlerContent);
const customExceptionsContent = `package ${basePackage}.exception
class ResourceNotFoundException(message: String) : RuntimeException(message)
class DuplicateResourceException(message: String) : RuntimeException(message)
class InvalidRequestException(message: String) : RuntimeException(message)
class UnauthorizedException(message: String) : RuntimeException(message)`;
await fs_1.promises.writeFile(path.join(exceptionDir, 'CustomExceptions.kt'), customExceptionsContent);
}
async generateWebSocket(srcDir, basePackage) {
const wsDir = path.join(srcDir, 'websocket');
await fs_1.promises.mkdir(wsDir, { recursive: true });
const wsConfigContent = `package ${basePackage}.websocket
import org.springframework.context.annotation.Configuration
import org.springframework.messaging.simp.config.MessageBrokerRegistry
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker
import org.springframework.web.socket.config.annotation.StompEndpointRegistry
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer
@Configuration
@EnableWebSocketMessageBroker
class WebSocketConfig : WebSocketMessageBrokerConfigurer {
override fun configureMessageBroker(config: MessageBrokerRegistry) {
config.enableSimpleBroker("/topic", "/queue")
config.setApplicationDestinationPrefixes("/app")
}
override fun registerStompEndpoints(registry: StompEndpointRegistry) {
registry.addEndpoint("/ws")
.setAllowedOriginPatterns("*")
.withSockJS()
}
}`;
await fs_1.promises.writeFile(path.join(wsDir, 'WebSocketConfig.kt'), wsConfigContent);
const wsControllerContent = `package ${basePackage}.websocket
import org.springframework.messaging.handler.annotation.MessageMapping
import org.springframework.messaging.handler.annotation.SendTo
import org.springframework.messaging.simp.SimpMessagingTemplate
import org.springframework.stereotype.Controller
import java.time.LocalDateTime
@Controller
class WebSocketController(
private val messagingTemplate: SimpMessagingTemplate
) {
@MessageMapping("/chat")
@SendTo("/topic/messages")
fun sendMessage(message: ChatMessage): ChatMessage {
return message.copy(timestamp = LocalDateTime.now())
}
fun sendToUser(username: String, message: ChatMessage) {
messagingTemplate.convertAndSendToUser(
username,
"/queue/messages",
message
)
}
}
data class ChatMessage(
val from: String,
val content: String,
val timestamp: LocalDateTime = LocalDateTime.now()
)`;
await fs_1.promises.writeFile(path.join(wsDir, 'WebSocketController.kt'), wsControllerContent);
}
async generateGraphQL(srcDir, basePackage) {
const graphqlDir = path.join(srcDir, 'graphql');
await fs_1.promises.mkdir(graphqlDir, { recursive: true });
const queryResolverContent = `package ${basePackage}.graphql
import ${basePackage}.dto.UserDto
import ${basePackage}.service.UserService
import org.springframework.graphql.data.method.annotation.Argument
import org.springframework.graphql.data.method.annotation.QueryMapping
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.stereotype.Controller
@Controller
class UserQueryResolver(
private val userService: UserService
) {
@QueryMapping
@PreAuthorize("hasRole('USER')")
fun user(@Argument id: Long): UserDto? {
return userService.findById(id)
}
@QueryMapping
@PreAuthorize("hasRole('ADMIN')")
fun users(@Argument page: Int = 0, @Argument size: Int = 10): List<UserDto> {
return userService.findAll(
org.springframework.data.domain.PageRequest.of(page, size)
).content
}
@QueryMapping
@PreAuthorize("hasRole('USER')")
fun me(): UserDto {
return userService.getCurrentUser()
}
}`;
await fs_1.promises.writeFile(path.join(graphqlDir, 'UserQueryResolver.kt'), queryResolverContent);
const mutationResolverContent = `package ${basePackage}.graphql
import ${basePackage}.dto.*
import ${basePackage}.service.AuthService
import ${basePackage}.service.UserService
import org.springframework.graphql.data.method.annotation.Argument
import org.springframework.graphql.data.method.annotation.MutationMapping
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.stereotype.Controller
@Controller
class UserMutationResolver(
private val authService: AuthService,
private val userService: UserService
) {
@MutationMapping
fun register(@Argument input: RegisterDto): TokenDto {
return authService.register(input)
}
@MutationMapping
fun login(@Argument input: LoginDto): TokenDto {
return authService.login(input)
}
@MutationMapping
@PreAuthorize("hasRole('USER')")
fun updateUser(
@Argument id: Long,
@Argument input: UpdateUserDto
): UserDto? {
return userService.update(id, input)
}
}`;
await fs_1.promises.writeFile(path.join(graphqlDir, 'UserMutationResolver.kt'), mutationResolverContent);
}
async generateUtils(srcDir, basePackage) {
const utilsDir = path.join(srcDir, 'utils');
await fs_1.promises.mkdir(utilsDir, { recursive: true });
const validationUtilsContent = `package ${basePackage}.utils
object ValidationUtils {
fun isValidEmail(email: String): Boolean {
val emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\$".toRegex()
return email.matches(emailRegex)
}
fun isStrongPassword(password: String): Boolean {
return password.length >= 8 &&
password.any { it.isUpperCase() } &&
password.any { it.isLowerCase() } &&
password.any { it.isDigit() } &&
password.any { !it.isLetterOrDigit() }
}
fun sanitizeInput(input: String): String {
return input.trim()
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'")
.replace("&", "&")
}
}`;
await fs_1.promises.writeFile(path.join(utilsDir, 'ValidationUtils.kt'), validationUtilsContent);
}
async generateRes