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.
This commit is contained in:
dalbodeule 2025-05-21 16:33:56 +09:00
parent 50f2844319
commit 35b02d156b
No known key found for this signature in database
GPG Key ID: EFA860D069C9FA65
21 changed files with 455 additions and 119 deletions

17
.idea/dataSources.xml generated Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="mydatabase@localhost" uuid="2f05e8c9-fa97-4b20-aa60-3de9e52721d6">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://localhost:55432/mydatabase</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

1
.idea/gradle.xml generated
View File

@ -8,6 +8,7 @@
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/debugBackend" />
</set>
</option>
</GradleProjectSettings>

6
.idea/kotlinc.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.1.10" />
</component>
</project>

View File

@ -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<org.springframework.boot.gradle.tasks.run.BootRun>("bootRun") {
systemProperty("spring.profiles.active", "dev")
}
kotlin {
compilerOptions {
freeCompilerArgs.addAll("-Xjsr305=strict")
}
}
tasks.withType<Test> {
useJUnitPlatform()
}

21
debugBackend/compose.yaml Normal file
View File

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

View File

@ -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<String>) {
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<DebugApplication>(*args) {
setDefaultProperties(envVars)
}
}

View File

@ -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()
}
}

View File

@ -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, "")
}

View File

@ -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<TestTable, String> {
}

View File

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

View File

@ -5,3 +5,5 @@
*/
rootProject.name = "snap-admin"
include("debugBackend")

View File

@ -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<Class<?>> 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);
}
}

View File

@ -424,4 +424,4 @@ public class SnapAdmin {
this.authenticated = authenticated;
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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());
}
}

View File

@ -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 {
// 필요한 속성이 있다면 정의
}

View File

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

View File

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

View File

@ -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;
/**

View File

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

View File

@ -39,8 +39,6 @@ import space.mori.dalbodeule.snapadmin.internal.model.UserAction;
*/
@Component
public class CustomActionRepositoryImpl implements CustomActionRepository {
@PersistenceContext(unitName = "internal")
private EntityManager entityManager;
/**

View File

@ -0,0 +1 @@
space.mori.dalbodeule.snapadmin.aot.SnapAdminRuntimeHints=space.mori.dalbodeule.snapadmin.aot.SnapAdminRuntimeHints