From 35b02d156b742c9073943a5ac37bdf527beb1fbf Mon Sep 17 00:00:00 2001 From: dalbodeule <11470513+dalbodeule@users.noreply.github.com> Date: Wed, 21 May 2025 16:33:56 +0900 Subject: [PATCH] Add debugBackend module with database and security setup Introduced a new `debugBackend` module to support debugging and development tasks. This includes configuration for PostgreSQL, Redis, Hibernate, Spring Boot, and a basic security setup. Also added required entities, repositories, and runtime hints for SnapAdmin integration. --- .idea/dataSources.xml | 17 ++ .idea/gradle.xml | 1 + .idea/kotlinc.xml | 6 + debugBackend/build.gradle.kts | 76 +++++++++ debugBackend/compose.yaml | 21 +++ .../mori/dalbodeule/debug/DebugApplication.kt | 32 ++++ .../dalbodeule/debug/config/SecurityConfig.kt | 43 ++++++ .../mori/dalbodeule/debug/model/TestTable.kt | 21 +++ .../debug/repository/TestTableRepository.kt | 7 + .../src/main/resources/application.yml | 18 +++ settings.gradle.kts | 2 + .../snapadmin/aot/SnapAdminRuntimeHints.java | 135 ++++++++++++++++ .../snapadmin/external/SnapAdmin.java | 2 +- .../external/SnapAdminAutoConfiguration.java | 145 +++++------------- .../annotations/SnapAdminEnabled.java | 14 ++ .../external/dbmapping/DbObjectSchema.java | 5 + .../internal/model/ConsoleQuery.java | 9 +- .../snapadmin/internal/model/UserAction.java | 13 +- .../snapadmin/internal/model/UserSetting.java | 4 + .../CustomActionRepositoryImpl.java | 2 - ....aot.hint.RuntimeHintsRegistrar.properties | 1 + 21 files changed, 455 insertions(+), 119 deletions(-) create mode 100644 .idea/dataSources.xml create mode 100644 .idea/kotlinc.xml create mode 100644 debugBackend/build.gradle.kts create mode 100644 debugBackend/compose.yaml create mode 100644 debugBackend/src/main/kotlin/space/mori/dalbodeule/debug/DebugApplication.kt create mode 100644 debugBackend/src/main/kotlin/space/mori/dalbodeule/debug/config/SecurityConfig.kt create mode 100644 debugBackend/src/main/kotlin/space/mori/dalbodeule/debug/model/TestTable.kt create mode 100644 debugBackend/src/main/kotlin/space/mori/dalbodeule/debug/repository/TestTableRepository.kt create mode 100644 debugBackend/src/main/resources/application.yml create mode 100644 src/main/java/space/mori/dalbodeule/snapadmin/aot/SnapAdminRuntimeHints.java create mode 100644 src/main/java/space/mori/dalbodeule/snapadmin/external/annotations/SnapAdminEnabled.java create mode 100644 src/main/resources/META-INF/spring/org.springframework.aot.hint.RuntimeHintsRegistrar.properties diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..16f5fd9 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,17 @@ + + + + + postgresql + true + org.postgresql.Driver + jdbc:postgresql://localhost:55432/mydatabase + + + + + + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index ce1c62c..9de6c56 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -8,6 +8,7 @@ diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..c22b6fa --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/debugBackend/build.gradle.kts b/debugBackend/build.gradle.kts new file mode 100644 index 0000000..152f29e --- /dev/null +++ b/debugBackend/build.gradle.kts @@ -0,0 +1,76 @@ +plugins { + kotlin("jvm") version "2.1.10" + kotlin("plugin.spring") version "2.1.10" + id("org.hibernate.orm") version "6.5.2.Final" + id("org.springframework.boot") version "3.4.5" + id("io.spring.dependency-management") version "1.1.7" + id("org.graalvm.buildtools.native") version "0.10.5" +} + +group = "space.mori.dalbodeule" +version = "0.5.1" + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +hibernate { + enhancement { + enableAssociationManagement.set(false) + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-actuator") + implementation("org.springframework.boot:spring-boot-starter-batch") + implementation("org.springframework.boot:spring-boot-starter-data-jpa") + implementation("org.springframework.boot:spring-boot-starter-data-redis") + implementation("org.springframework.boot:spring-boot-starter-security") + implementation("org.jetbrains.kotlin:kotlin-reflect") + implementation("org.springframework.boot:spring-boot-starter-websocket") + implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.6") + + implementation("io.swagger.core.v3:swagger-core:2.2.30") + implementation("io.swagger.core.v3:swagger-annotations:2.2.30") + + implementation("io.github.cdimascio:dotenv-kotlin:6.4.1") + + developmentOnly("org.springframework.boot:spring-boot-docker-compose") + runtimeOnly("org.postgresql:postgresql:42.7.4") + + implementation("jakarta.xml.bind:jakarta.xml.bind-api:4.0.2") + implementation("javax.xml.bind:jaxb-api:2.3.1") + + // HTTP 클라이언트 + implementation("org.springframework.boot:spring-boot-starter-webflux") + + implementation(rootProject) + + testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") + testImplementation("org.springframework.batch:spring-batch-test") + testImplementation("org.springframework.security:spring-security-test") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + + testImplementation(kotlin("test")) +} + +tasks.named("bootRun") { + systemProperty("spring.profiles.active", "dev") +} + +kotlin { + compilerOptions { + freeCompilerArgs.addAll("-Xjsr305=strict") + } +} + +tasks.withType { + useJUnitPlatform() +} diff --git a/debugBackend/compose.yaml b/debugBackend/compose.yaml new file mode 100644 index 0000000..b2e8c7c --- /dev/null +++ b/debugBackend/compose.yaml @@ -0,0 +1,21 @@ +services: + pgvector: + image: 'pgvector/pgvector:pg16' + environment: + - 'POSTGRES_DB=mydatabase' + - 'POSTGRES_PASSWORD=secret' + - 'POSTGRES_USER=myuser' + labels: + - "org.springframework.boot.service-connection=postgres" + ports: + - target: 5432 + published: 55432 + protocol: tcp + volumes: + - postgresql:/var/lib/postgresql/data + redis: + image: 'redis:latest' + ports: + - '6379' +volumes: + postgresql: \ No newline at end of file diff --git a/debugBackend/src/main/kotlin/space/mori/dalbodeule/debug/DebugApplication.kt b/debugBackend/src/main/kotlin/space/mori/dalbodeule/debug/DebugApplication.kt new file mode 100644 index 0000000..b64a8e7 --- /dev/null +++ b/debugBackend/src/main/kotlin/space/mori/dalbodeule/debug/DebugApplication.kt @@ -0,0 +1,32 @@ +package space.mori.dalbodeule.debug + +import io.github.cdimascio.dotenv.dotenv +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.domain.EntityScan +import org.springframework.boot.runApplication +import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import space.mori.dalbodeule.snapadmin.external.annotations.SnapAdminEnabled + +val dotenv = dotenv { + ignoreIfMissing = true +} + +@SnapAdminEnabled +@SpringBootApplication +@EnableJpaRepositories(basePackages = ["space.mori.dalbodeule.debug.repository"]) +@EntityScan(basePackages = ["space.mori.dalbodeule.debug.model"]) +class DebugApplication + +fun main(args: Array) { + val envVars = mapOf( + "DB_HOST" to dotenv["DB_HOST"], + "DB_PORT" to dotenv["DB_PORT"], + "DB_NAME" to dotenv["DB_NAME"], + "DB_USER" to dotenv["DB_USER"], + "DB_PASSWORD" to dotenv["DB_PASSWORD"] + ) + + runApplication(*args) { + setDefaultProperties(envVars) + } +} \ No newline at end of file diff --git a/debugBackend/src/main/kotlin/space/mori/dalbodeule/debug/config/SecurityConfig.kt b/debugBackend/src/main/kotlin/space/mori/dalbodeule/debug/config/SecurityConfig.kt new file mode 100644 index 0000000..20eed83 --- /dev/null +++ b/debugBackend/src/main/kotlin/space/mori/dalbodeule/debug/config/SecurityConfig.kt @@ -0,0 +1,43 @@ +package space.mori.dalbodeule.debug.config + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.core.userdetails.User +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.provisioning.InMemoryUserDetailsManager +import org.springframework.security.web.SecurityFilterChain + +@Configuration +@EnableWebSecurity +class SecurityConfig { + + @Bean + fun filterChain(http: HttpSecurity): SecurityFilterChain { + return http + .csrf { it.disable() } + .authorizeHttpRequests { + it.anyRequest().authenticated() + } + .httpBasic {} + .build() + } + + @Bean + fun userDetailsService(passwordEncoder: PasswordEncoder): UserDetailsService { + val admin = User.builder() + .username("test@gmail.com") + .password(passwordEncoder.encode("password")) + .roles("ADMIN") + .build() + return InMemoryUserDetailsManager(admin) + } + + @Bean + fun passwordEncoder(): PasswordEncoder { + return BCryptPasswordEncoder() + } +} \ No newline at end of file diff --git a/debugBackend/src/main/kotlin/space/mori/dalbodeule/debug/model/TestTable.kt b/debugBackend/src/main/kotlin/space/mori/dalbodeule/debug/model/TestTable.kt new file mode 100644 index 0000000..99c4d4e --- /dev/null +++ b/debugBackend/src/main/kotlin/space/mori/dalbodeule/debug/model/TestTable.kt @@ -0,0 +1,21 @@ +package space.mori.dalbodeule.debug.model + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table + +@Entity +@Table(name="test_table") +data class TestTable( + @Id + @GeneratedValue(strategy = GenerationType.UUID) + var id: String? = null, + + @Column(nullable = false, length = 32) + var name: String +) { + constructor(): this(null, "") +} \ No newline at end of file diff --git a/debugBackend/src/main/kotlin/space/mori/dalbodeule/debug/repository/TestTableRepository.kt b/debugBackend/src/main/kotlin/space/mori/dalbodeule/debug/repository/TestTableRepository.kt new file mode 100644 index 0000000..02dfde5 --- /dev/null +++ b/debugBackend/src/main/kotlin/space/mori/dalbodeule/debug/repository/TestTableRepository.kt @@ -0,0 +1,7 @@ +package space.mori.dalbodeule.debug.repository + +import org.springframework.data.jpa.repository.JpaRepository +import space.mori.dalbodeule.debug.model.TestTable + +interface TestTableRepository: JpaRepository { +} \ No newline at end of file diff --git a/debugBackend/src/main/resources/application.yml b/debugBackend/src/main/resources/application.yml new file mode 100644 index 0000000..bfd6ccd --- /dev/null +++ b/debugBackend/src/main/resources/application.yml @@ -0,0 +1,18 @@ +spring: + datasource: + url: jdbc:postgresql://${DB_HOST}:${DB_PORT}/${DB_NAME} + username: ${DB_USER} + password: ${DB_PASSWORD} + driver-class-name: org.postgresql.Driver + jpa: + hibernate: + ddl-auto: update + show-sql: true + database-platform: org.hibernate.dialect.PostgreSQLDialect +snapadmin: + enabled: true + baseUrl: admin + models-package: space.mori.dalbodeule.debug.model +logging: + level: + root: INFO \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 3b6483c..3005147 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,3 +5,5 @@ */ rootProject.name = "snap-admin" + +include("debugBackend") \ No newline at end of file diff --git a/src/main/java/space/mori/dalbodeule/snapadmin/aot/SnapAdminRuntimeHints.java b/src/main/java/space/mori/dalbodeule/snapadmin/aot/SnapAdminRuntimeHints.java new file mode 100644 index 0000000..b761d73 --- /dev/null +++ b/src/main/java/space/mori/dalbodeule/snapadmin/aot/SnapAdminRuntimeHints.java @@ -0,0 +1,135 @@ +package space.mori.dalbodeule.snapadmin.aot; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import space.mori.dalbodeule.snapadmin.external.SnapAdmin; +import space.mori.dalbodeule.snapadmin.external.SnapAdminProperties; +import space.mori.dalbodeule.snapadmin.external.annotations.Disable; +import space.mori.dalbodeule.snapadmin.external.annotations.DisableEditField; +import space.mori.dalbodeule.snapadmin.external.annotations.DisplayFormat; +import space.mori.dalbodeule.snapadmin.external.annotations.DisplayImage; // Assuming this is used +import space.mori.dalbodeule.snapadmin.external.annotations.HiddenEditForm; // Assuming this is used +import space.mori.dalbodeule.snapadmin.external.dbmapping.CustomJpaRepository; +import space.mori.dalbodeule.snapadmin.external.dbmapping.DbObjectSchema; +import space.mori.dalbodeule.snapadmin.external.dbmapping.fields.*; +import space.mori.dalbodeule.snapadmin.external.dto.MappingError; +import space.mori.dalbodeule.snapadmin.external.misc.Utils; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import static org.springframework.aot.hint.MemberCategory.*; + +public class SnapAdminRuntimeHints implements RuntimeHintsRegistrar { + + private static final Set> dbFieldTypes = new HashSet<>(Arrays.asList( + BooleanFieldType.class, LongFieldType.class, IntegerFieldType.class, + BigIntegerFieldType.class, ShortFieldType.class, StringFieldType.class, + LocalDateFieldType.class, DateFieldType.class, LocalDateTimeFieldType.class, + InstantFieldType.class, FloatFieldType.class, DoubleFieldType.class, + BigDecimalFieldType.class, ByteArrayFieldType.class, OffsetDateTimeFieldType.class, + ByteFieldType.class, UUIDFieldType.class, CharFieldType.class, + EnumFieldType.class, TextFieldType.class + // Add any other concrete DbFieldType implementations here + )); + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + // Register SnapAdmin's own classes + hints.reflection().registerType(SnapAdmin.class, INTROSPECT_DECLARED_METHODS, INVOKE_DECLARED_METHODS); + hints.reflection().registerType(SnapAdminProperties.class, INVOKE_DECLARED_CONSTRUCTORS, INVOKE_PUBLIC_METHODS); // For Spring binding + + hints.reflection().registerType(DbObjectSchema.class, INVOKE_DECLARED_CONSTRUCTORS, INTROSPECT_DECLARED_METHODS, INVOKE_PUBLIC_METHODS); + // CustomJpaRepository 인터페이스 자체는 생성자 호출 힌트가 불필요할 수 있음 + hints.reflection().registerType(DbField.class, INVOKE_DECLARED_CONSTRUCTORS, INTROSPECT_DECLARED_METHODS, INVOKE_PUBLIC_METHODS); + hints.reflection().registerType(MappingError.class, INVOKE_DECLARED_CONSTRUCTORS); + // hints.reflection().registerType(Utils.class); // 사용 패턴 확인 후 필요하면 활성화 + + // Register DbFieldType and its subclasses for default constructor invocation + hints.reflection().registerType(DbFieldType.class); + for (Class dbFieldTypeClass : dbFieldTypes) { + hints.reflection().registerType(dbFieldTypeClass, INVOKE_DECLARED_CONSTRUCTORS); + } + // EnumFieldType has a special constructor too + hints.reflection().registerType(EnumFieldType.class, INVOKE_DECLARED_CONSTRUCTORS); + + + // Register SnapAdmin's custom annotations (and assume their attributes might be read) + registerAnnotation(hints, Disable.class); + registerAnnotation(hints, DisableEditField.class); + registerAnnotation(hints, DisplayFormat.class); + registerAnnotation(hints, DisplayImage.class); + registerAnnotation(hints, HiddenEditForm.class); + + // Register Jakarta Persistence annotations (and assume their attributes might be read) + registerAnnotation(hints, jakarta.persistence.Entity.class); + registerAnnotation(hints, jakarta.persistence.Id.class); + registerAnnotation(hints, jakarta.persistence.Column.class); + registerAnnotation(hints, jakarta.persistence.Lob.class); + registerAnnotation(hints, jakarta.persistence.Enumerated.class); + registerAnnotation(hints, jakarta.persistence.EnumType.class); // TYPE_VISIBLE 제거 + registerAnnotation(hints, jakarta.persistence.OneToMany.class); + registerAnnotation(hints, jakarta.persistence.ManyToMany.class); + registerAnnotation(hints, jakarta.persistence.ManyToOne.class); + registerAnnotation(hints, jakarta.persistence.OneToOne.class); + registerAnnotation(hints, jakarta.persistence.JoinColumn.class); + // Add other JPA annotations if used, e.g. @Table, @Transient + + // Hints for operations on arbitrary (user-defined) @Entity classes + // 가능하면 스캔 범위를 제한하거나, 필요한 메서드만 등록 + // 예시: 특정 패키지 내의 @Entity 클래스 스캔 + // ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); + // scanner.addIncludeFilter(new AnnotationTypeFilter(jakarta.persistence.Entity.class)); + // for (BeanDefinition bd : scanner.findCandidateComponents("com.example.entities")) { + // try { + // Class entityClass = Class.forName(bd.getBeanClassName()); + // hints.reflection().registerType(entityClass, INTROSPECT_DECLARED_FIELDS, INVOKE_DECLARED_METHODS); + // } catch (ClassNotFoundException e) { + // // Handle exception + // } + // } + // Register SnapAdmin's own classes + hints.reflection().registerType(SnapAdmin.class, INTROSPECT_DECLARED_METHODS, INVOKE_DECLARED_METHODS); + hints.reflection().registerType(SnapAdminProperties.class, INVOKE_DECLARED_CONSTRUCTORS, INVOKE_PUBLIC_METHODS); // For Spring binding + + hints.reflection().registerType(DbObjectSchema.class, INVOKE_DECLARED_CONSTRUCTORS, INTROSPECT_DECLARED_METHODS, INVOKE_PUBLIC_METHODS); + hints.reflection().registerType(CustomJpaRepository.class, INVOKE_DECLARED_CONSTRUCTORS); + hints.reflection().registerType(DbField.class, INVOKE_DECLARED_CONSTRUCTORS, INTROSPECT_DECLARED_METHODS, INVOKE_PUBLIC_METHODS); + hints.reflection().registerType(MappingError.class, INVOKE_DECLARED_CONSTRUCTORS); + hints.reflection().registerType(Utils.class); // If it contains static methods called, or if instantiated + + + // For Class.forName(className) on unknown classes (typically user entities) + // and subsequent operations like getDeclaredFields(), getAnnotation(), newInstance() + // This is a general hint. Users should still ensure their entities are hinted. + // Consider making this more specific if possible, e.g., by scanning packages if configured. + hints.reflection().registerType(Object.class, + INTROSPECT_DECLARED_CONSTRUCTORS, INVOKE_DECLARED_CONSTRUCTORS, + INTROSPECT_DECLARED_METHODS, INVOKE_DECLARED_METHODS, // For getters/setters if library invokes them + DECLARED_FIELDS // For field access + ); + + // For ClassPathScanningCandidateComponentProvider + hints.reflection().registerType(org.springframework.beans.factory.config.BeanDefinition.class, INVOKE_PUBLIC_METHODS); // For getBeanClassName() + hints.reflection().registerType(org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.class, INVOKE_DECLARED_CONSTRUCTORS); + hints.reflection().registerType(org.springframework.core.type.filter.AnnotationTypeFilter.class, INVOKE_DECLARED_CONSTRUCTORS); + + + // Resource hints if any .properties or .xml files are loaded from classpath by the library + // hints.resources().registerPattern("my-library-config.xml"); + + // Proxy hints if JDK proxies are created for library interfaces + // hints.proxies().registerJdkProxy(MyLibraryInterface.class); + + // Serialization hints if objects are serialized by the library + // hints.serialization().registerType(MySerializableObject.class); + } + + private void registerAnnotation(RuntimeHints hints, Class annotationType) { + hints.reflection().registerType(annotationType, INVOKE_DECLARED_METHODS); + } + private void registerAnnotation(RuntimeHints hints, Class annotationType, org.springframework.aot.hint.MemberCategory... categories) { + hints.reflection().registerType(annotationType, categories); + } +} \ No newline at end of file diff --git a/src/main/java/space/mori/dalbodeule/snapadmin/external/SnapAdmin.java b/src/main/java/space/mori/dalbodeule/snapadmin/external/SnapAdmin.java index 428347a..fcc550e 100644 --- a/src/main/java/space/mori/dalbodeule/snapadmin/external/SnapAdmin.java +++ b/src/main/java/space/mori/dalbodeule/snapadmin/external/SnapAdmin.java @@ -424,4 +424,4 @@ public class SnapAdmin { this.authenticated = authenticated; } -} +} \ No newline at end of file diff --git a/src/main/java/space/mori/dalbodeule/snapadmin/external/SnapAdminAutoConfiguration.java b/src/main/java/space/mori/dalbodeule/snapadmin/external/SnapAdminAutoConfiguration.java index de69527..e670511 100644 --- a/src/main/java/space/mori/dalbodeule/snapadmin/external/SnapAdminAutoConfiguration.java +++ b/src/main/java/space/mori/dalbodeule/snapadmin/external/SnapAdminAutoConfiguration.java @@ -1,118 +1,55 @@ -/* - * SnapAdmin - An automatically generated CRUD admin UI for Spring Boot apps - * Copyright (C) 2023 Ailef (http://ailef.tech) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - package space.mori.dalbodeule.snapadmin.external; -import java.util.Properties; - -import javax.sql.DataSource; - -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.jdbc.DataSourceBuilder; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -import org.springframework.orm.jpa.JpaTransactionManager; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.annotation.EnableTransactionManagement; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.support.TransactionTemplate; - +import space.mori.dalbodeule.snapadmin.external.controller.DataExportController; +import space.mori.dalbodeule.snapadmin.external.controller.FileDownloadController; +import space.mori.dalbodeule.snapadmin.external.controller.GlobalController; +import space.mori.dalbodeule.snapadmin.external.controller.SnapAdminController; +import space.mori.dalbodeule.snapadmin.external.controller.rest.AutocompleteController; +import space.mori.dalbodeule.snapadmin.external.dbmapping.CustomJpaRepository; +import space.mori.dalbodeule.snapadmin.external.dbmapping.DbObjectSchema; +import space.mori.dalbodeule.snapadmin.external.dbmapping.SnapAdminRepository; import space.mori.dalbodeule.snapadmin.internal.InternalSnapAdminConfiguration; +import space.mori.dalbodeule.snapadmin.internal.UserConfiguration; +import space.mori.dalbodeule.snapadmin.internal.service.ConsoleQueryService; +import space.mori.dalbodeule.snapadmin.internal.service.UserActionService; +import space.mori.dalbodeule.snapadmin.internal.service.UserSettingsService; /** - * The configuration class for "internal" data source. This is not the - * source connected to the user's data/entities, but rather an internal - * H2 database which is used by SnapAdmin to store user - * settings and other information like operations history. + * SnapAdmin 자동 설정 클래스. 메인 애플리케이션의 JPA 설정을 재사용합니다. */ -@ConditionalOnProperty(name = "snapadmin.enabled", matchIfMissing = false) -@ComponentScan -@EnableConfigurationProperties(SnapAdminProperties.class) @Configuration -@EnableJpaRepositories( - entityManagerFactoryRef = "internalEntityManagerFactory", - basePackages = { "space.mori.dalbodeule.snapadmin.internal.repository" } -) -@EnableTransactionManagement -@Import(InternalSnapAdminConfiguration.class) +@ConditionalOnProperty(name = "snapadmin.enabled", havingValue = "true", matchIfMissing = false) +@EnableConfigurationProperties(SnapAdminProperties.class) +@EnableJpaRepositories(basePackages = "space.mori.dalbodeule.snapadmin.internal.repository") +@EntityScan(basePackages = "space.mori.dalbodeule.snapadmin.internal.model") +@Import({ + SnapAdmin.class, + SnapAdminMvcConfig.class, + StartupAuthCheckRunner.class, + ThymeleafUtils.class, + + // controllers + SnapAdminController.class, + DataExportController.class, + FileDownloadController.class, + GlobalController.class, + AutocompleteController.class, + + // dbmapping + SnapAdminRepository.class, + + // internals + ConsoleQueryService.class, + UserActionService.class, + UserSettingsService.class, + InternalSnapAdminConfiguration.class, + UserConfiguration.class +}) public class SnapAdminAutoConfiguration { - @Autowired - private SnapAdminProperties props; - - /** - * Builds and returns the internal data source. - * - * @return - */ - @Bean - DataSource internalDataSource() { - DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create(); - dataSourceBuilder.driverClassName("org.h2.Driver"); - if (props.isTestMode()) { - dataSourceBuilder.url("jdbc:h2:mem:test"); - } else { - dataSourceBuilder.url("jdbc:h2:file:./snapadmin_internal"); - } - - dataSourceBuilder.username("sa"); - dataSourceBuilder.password("password"); - return dataSourceBuilder.build(); - } - - @Bean - LocalContainerEntityManagerFactoryBean internalEntityManagerFactory() { - LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean(); - factoryBean.setDataSource(internalDataSource()); - factoryBean.setPersistenceUnitName("internal"); - factoryBean.setPackagesToScan("space.mori.dalbodeule.snapadmin.internal.model"); - factoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); - Properties properties = new Properties(); - properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect"); - properties.setProperty("hibernate.hbm2ddl.auto", "update"); - factoryBean.setJpaProperties(properties); - factoryBean.afterPropertiesSet(); - return factoryBean; - } - - /** - * The internal transaction manager. It is not defined as a bean - * in order to avoid "colliding" with the default transactionManager - * registered by the user. Internally, we use this to instantiate a - * TransactionTemplate and run all transactions manually instead of - * relying on the @link {@link Transactional} annotation. - * @return - */ - PlatformTransactionManager internalTransactionManager() { - JpaTransactionManager transactionManager = new JpaTransactionManager(); - transactionManager.setEntityManagerFactory(internalEntityManagerFactory().getObject()); - return transactionManager; - } - - @Bean - TransactionTemplate internalTransactionTemplate() { - return new TransactionTemplate(internalTransactionManager()); - } - } \ No newline at end of file diff --git a/src/main/java/space/mori/dalbodeule/snapadmin/external/annotations/SnapAdminEnabled.java b/src/main/java/space/mori/dalbodeule/snapadmin/external/annotations/SnapAdminEnabled.java new file mode 100644 index 0000000..5083cfd --- /dev/null +++ b/src/main/java/space/mori/dalbodeule/snapadmin/external/annotations/SnapAdminEnabled.java @@ -0,0 +1,14 @@ +package space.mori.dalbodeule.snapadmin.external.annotations; + +import org.springframework.context.annotation.Import; +import space.mori.dalbodeule.snapadmin.external.SnapAdminAutoConfiguration; + +import java.lang.annotation.*; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(SnapAdminAutoConfiguration.class) // SnapAdmin 설정 클래스를 Import +public @interface SnapAdminEnabled { + // 필요한 속성이 있다면 정의 +} \ No newline at end of file diff --git a/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/DbObjectSchema.java b/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/DbObjectSchema.java index 12ca282..1fc0807 100644 --- a/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/DbObjectSchema.java +++ b/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/DbObjectSchema.java @@ -35,6 +35,7 @@ import org.springframework.web.multipart.MultipartFile; import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.persistence.Entity; import jakarta.persistence.ManyToMany; import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; @@ -99,6 +100,10 @@ public class DbObjectSchema { * @param snapAdmin the SnapAdmin instance */ public DbObjectSchema(Class klass, SnapAdmin snapAdmin) { + if (klass.getAnnotation(Entity.class) == null) { + throw new SnapAdminException("Class " + klass.getName() + " is not an @Entity"); + } + this.snapAdmin = snapAdmin; this.entityClass = klass; diff --git a/src/main/java/space/mori/dalbodeule/snapadmin/internal/model/ConsoleQuery.java b/src/main/java/space/mori/dalbodeule/snapadmin/internal/model/ConsoleQuery.java index c4ec1d1..2228de8 100644 --- a/src/main/java/space/mori/dalbodeule/snapadmin/internal/model/ConsoleQuery.java +++ b/src/main/java/space/mori/dalbodeule/snapadmin/internal/model/ConsoleQuery.java @@ -22,19 +22,20 @@ package space.mori.dalbodeule.snapadmin.internal.model; import java.time.LocalDateTime; +import jakarta.persistence.*; import org.hibernate.annotations.UuidGenerator; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Lob; +import space.mori.dalbodeule.snapadmin.external.annotations.Disable; @Entity +@Disable +@Table(name="snapadmin_console_query") public class ConsoleQuery { @Id @UuidGenerator private String id; - @Lob + @Column(columnDefinition = "TEXT") private String sql; private String title; diff --git a/src/main/java/space/mori/dalbodeule/snapadmin/internal/model/UserAction.java b/src/main/java/space/mori/dalbodeule/snapadmin/internal/model/UserAction.java index 7ced690..a7bb9d9 100644 --- a/src/main/java/space/mori/dalbodeule/snapadmin/internal/model/UserAction.java +++ b/src/main/java/space/mori/dalbodeule/snapadmin/internal/model/UserAction.java @@ -21,14 +21,10 @@ package space.mori.dalbodeule.snapadmin.internal.model; import java.time.LocalDateTime; +import jakarta.persistence.*; import org.springframework.format.datetime.standard.DateTimeFormatterFactory; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.Lob; +import space.mori.dalbodeule.snapadmin.external.annotations.Disable; /** * An write operation executed by a user from the web UI. This class @@ -36,6 +32,8 @@ import jakarta.persistence.Lob; * concrete yet (e.g. a diff or SQL query) about what change was performed. */ @Entity +@Disable +@Table(name="snapadmin_user_action") public class UserAction { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -51,8 +49,7 @@ public class UserAction { * The SQL query generated by the operation. * This field is here but it's NOT currently supported */ - @Lob - @Column(nullable = false) + @Column(nullable = false, columnDefinition = "TEXT") private String sql; /** diff --git a/src/main/java/space/mori/dalbodeule/snapadmin/internal/model/UserSetting.java b/src/main/java/space/mori/dalbodeule/snapadmin/internal/model/UserSetting.java index d8b855e..b3b1027 100644 --- a/src/main/java/space/mori/dalbodeule/snapadmin/internal/model/UserSetting.java +++ b/src/main/java/space/mori/dalbodeule/snapadmin/internal/model/UserSetting.java @@ -21,11 +21,15 @@ package space.mori.dalbodeule.snapadmin.internal.model; import jakarta.persistence.Entity; import jakarta.persistence.Id; +import jakarta.persistence.Table; +import space.mori.dalbodeule.snapadmin.external.annotations.Disable; /** * A single variable in the user settings. */ @Entity +@Disable +@Table(name="snapadmin_user_setting") public class UserSetting { /** * The id of the variable (its name) diff --git a/src/main/java/space/mori/dalbodeule/snapadmin/internal/repository/CustomActionRepositoryImpl.java b/src/main/java/space/mori/dalbodeule/snapadmin/internal/repository/CustomActionRepositoryImpl.java index f506265..7503cde 100644 --- a/src/main/java/space/mori/dalbodeule/snapadmin/internal/repository/CustomActionRepositoryImpl.java +++ b/src/main/java/space/mori/dalbodeule/snapadmin/internal/repository/CustomActionRepositoryImpl.java @@ -39,8 +39,6 @@ import space.mori.dalbodeule.snapadmin.internal.model.UserAction; */ @Component public class CustomActionRepositoryImpl implements CustomActionRepository { - - @PersistenceContext(unitName = "internal") private EntityManager entityManager; /** diff --git a/src/main/resources/META-INF/spring/org.springframework.aot.hint.RuntimeHintsRegistrar.properties b/src/main/resources/META-INF/spring/org.springframework.aot.hint.RuntimeHintsRegistrar.properties new file mode 100644 index 0000000..28d3d16 --- /dev/null +++ b/src/main/resources/META-INF/spring/org.springframework.aot.hint.RuntimeHintsRegistrar.properties @@ -0,0 +1 @@ +space.mori.dalbodeule.snapadmin.aot.SnapAdminRuntimeHints=space.mori.dalbodeule.snapadmin.aot.SnapAdminRuntimeHints \ No newline at end of file