diff --git a/.idea/snap-admin.iml b/.idea/snap-admin.iml new file mode 100644 index 0000000..04eef97 --- /dev/null +++ b/.idea/snap-admin.iml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 1cf478b..5a199c5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -49,6 +49,8 @@ dependencies { api(libs.org.springframework.boot.spring.boot.starter.validation) api(libs.org.springframework.boot.spring.boot.starter.web) api(libs.org.springframework.boot.spring.boot.configuration.processor) + api("io.swagger.core.v3:swagger-annotations:2.2.15") + testImplementation(libs.org.springframework.boot.spring.boot.starter.test) } 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 d5a9d14..1ef7d2c 100644 --- a/src/main/java/space/mori/dalbodeule/snapadmin/external/SnapAdmin.java +++ b/src/main/java/space/mori/dalbodeule/snapadmin/external/SnapAdmin.java @@ -51,6 +51,7 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; 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.dbmapping.CustomJpaRepository; import space.mori.dalbodeule.snapadmin.external.dbmapping.DbObjectSchema; @@ -219,6 +220,7 @@ public class SnapAdmin { Field[] fields = klass.getDeclaredFields(); for (Field f : fields) { try { + if(f.getName().contains("hibernate")) continue; DbField field = mapField(f, schema); field.setSchema(schema); schema.addField(field); @@ -352,8 +354,9 @@ public class SnapAdmin { } DisplayFormat displayFormat = f.getAnnotation(DisplayFormat.class); + DisableEditField disableEdit = f.getAnnotation(DisableEditField.class); - DbField field = new DbField(f.getName(), fieldName, f, fieldType, schema, displayFormat != null ? displayFormat.format() : null); + DbField field = new DbField(f.getName(), fieldName, f, fieldType, schema, displayFormat != null ? displayFormat.format() : null, disableEdit != null); field.setConnectedType(connectedType); Id[] idAnnotations = f.getAnnotationsByType(Id.class); diff --git a/src/main/java/space/mori/dalbodeule/snapadmin/external/annotations/DisableEdit.java b/src/main/java/space/mori/dalbodeule/snapadmin/external/annotations/DisableEditField.java similarity index 94% rename from src/main/java/space/mori/dalbodeule/snapadmin/external/annotations/DisableEdit.java rename to src/main/java/space/mori/dalbodeule/snapadmin/external/annotations/DisableEditField.java index b08c8df..0e774c9 100644 --- a/src/main/java/space/mori/dalbodeule/snapadmin/external/annotations/DisableEdit.java +++ b/src/main/java/space/mori/dalbodeule/snapadmin/external/annotations/DisableEditField.java @@ -28,6 +28,6 @@ import java.lang.annotation.Target; * Disables edit actions on the Entity class. */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface DisableEdit { +@Target(ElementType.FIELD) +public @interface DisableEditField { } \ No newline at end of file diff --git a/src/main/java/space/mori/dalbodeule/snapadmin/external/annotations/HiddenEditForm.java b/src/main/java/space/mori/dalbodeule/snapadmin/external/annotations/HiddenEditForm.java new file mode 100644 index 0000000..2b6e3ce --- /dev/null +++ b/src/main/java/space/mori/dalbodeule/snapadmin/external/annotations/HiddenEditForm.java @@ -0,0 +1,11 @@ +package space.mori.dalbodeule.snapadmin.external.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface HiddenEditForm { +} diff --git a/src/main/java/space/mori/dalbodeule/snapadmin/external/controller/DataExportController.java b/src/main/java/space/mori/dalbodeule/snapadmin/external/controller/DataExportController.java index bfa7639..ee99196 100644 --- a/src/main/java/space/mori/dalbodeule/snapadmin/external/controller/DataExportController.java +++ b/src/main/java/space/mori/dalbodeule/snapadmin/external/controller/DataExportController.java @@ -29,6 +29,7 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import io.swagger.v3.oas.annotations.Hidden; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVPrinter; import org.apache.poi.ss.usermodel.Cell; @@ -75,6 +76,7 @@ import space.mori.dalbodeule.snapadmin.internal.repository.ConsoleQueryRepositor @Controller @RequestMapping(value = { "/${snapadmin.baseUrl}/", "/${snapadmin.baseUrl}" }) @Import(ObjectMapper.class) +@Hidden public class DataExportController { private static final Logger logger = LoggerFactory.getLogger(DataExportFormat.class); private final SnapAdmin snapAdmin; diff --git a/src/main/java/space/mori/dalbodeule/snapadmin/external/controller/FileDownloadController.java b/src/main/java/space/mori/dalbodeule/snapadmin/external/controller/FileDownloadController.java index 53aaf07..83bcba1 100644 --- a/src/main/java/space/mori/dalbodeule/snapadmin/external/controller/FileDownloadController.java +++ b/src/main/java/space/mori/dalbodeule/snapadmin/external/controller/FileDownloadController.java @@ -21,6 +21,7 @@ package space.mori.dalbodeule.snapadmin.external.controller; import java.util.Optional; +import io.swagger.v3.oas.annotations.Hidden; import org.apache.tika.Tika; import org.apache.tika.mime.MimeTypeException; import org.apache.tika.mime.MimeTypes; @@ -48,6 +49,7 @@ import space.mori.dalbodeule.snapadmin.external.exceptions.SnapAdminException; */ @Controller @RequestMapping(value = {"/${snapadmin.baseUrl}/download", "/${snapadmin.baseUrl}/download/"}) +@Hidden public class FileDownloadController { @Autowired private SnapAdminRepository repository; diff --git a/src/main/java/space/mori/dalbodeule/snapadmin/external/controller/SnapAdminController.java b/src/main/java/space/mori/dalbodeule/snapadmin/external/controller/SnapAdminController.java index 5058658..46cb303 100644 --- a/src/main/java/space/mori/dalbodeule/snapadmin/external/controller/SnapAdminController.java +++ b/src/main/java/space/mori/dalbodeule/snapadmin/external/controller/SnapAdminController.java @@ -32,6 +32,7 @@ import java.util.Random; import java.util.Set; import java.util.stream.Collectors; +import io.swagger.v3.oas.annotations.Hidden; import org.hibernate.id.IdentifierGenerationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -86,6 +87,7 @@ import space.mori.dalbodeule.snapadmin.internal.service.UserSettingsService; */ @Controller @RequestMapping(value= {"/${snapadmin.baseUrl}", "/${snapadmin.baseUrl}/"}) +@Hidden public class SnapAdminController { private static final Logger logger = LoggerFactory.getLogger(SnapAdminController.class); @@ -527,9 +529,14 @@ public class SnapAdminController { } else { throw new RuntimeException(e); } + } catch (Exception e) { + // 추가: 일반적인 예외 처리 + logger.error("Unexpected error during data submission: ", e); + attr.addFlashAttribute("errorTitle", "System Error"); + attr.addFlashAttribute("error", e.getMessage()); + attr.addFlashAttribute("params", params); } - if (attr.getFlashAttributes().containsKey("error")) { if (create) return "redirect:/" + properties.getBaseUrl() + "/model/" + schema.getClassName() + "/create"; diff --git a/src/main/java/space/mori/dalbodeule/snapadmin/external/controller/rest/AutocompleteController.java b/src/main/java/space/mori/dalbodeule/snapadmin/external/controller/rest/AutocompleteController.java index fe2c652..07098b2 100644 --- a/src/main/java/space/mori/dalbodeule/snapadmin/external/controller/rest/AutocompleteController.java +++ b/src/main/java/space/mori/dalbodeule/snapadmin/external/controller/rest/AutocompleteController.java @@ -22,6 +22,7 @@ package space.mori.dalbodeule.snapadmin.external.controller.rest; import java.util.List; import java.util.stream.Collectors; +import io.swagger.v3.oas.annotations.Hidden; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -40,6 +41,7 @@ import space.mori.dalbodeule.snapadmin.external.dto.AutocompleteSearchResult; */ @RestController @RequestMapping(value= {"/${snapadmin.baseUrl}/api/autocomplete", "/${snapadmin.baseUrl}/api/autocomplete/"}) +@Hidden public class AutocompleteController { @Autowired private SnapAdmin snapAdmin; diff --git a/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/CustomJpaRepository.java b/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/CustomJpaRepository.java index e85b190..1cebfb8 100644 --- a/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/CustomJpaRepository.java +++ b/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/CustomJpaRepository.java @@ -123,6 +123,7 @@ public class CustomJpaRepository extends SimpleJpaRepository { for (DbField field : schema.getSortedFields()) { if (field.isPrimaryKey()) continue; if (field.isReadOnly()) continue; + if (field.isDisableEditField()) continue; boolean keepValue = params.getOrDefault("__keep_" + field.getName(), "off").equals("on"); if (keepValue) continue; 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 e61136d..12ca282 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 @@ -43,7 +43,7 @@ import space.mori.dalbodeule.snapadmin.external.SnapAdmin; import space.mori.dalbodeule.snapadmin.external.annotations.ComputedColumn; import space.mori.dalbodeule.snapadmin.external.annotations.DisableCreate; import space.mori.dalbodeule.snapadmin.external.annotations.DisableDelete; -import space.mori.dalbodeule.snapadmin.external.annotations.DisableEdit; +import space.mori.dalbodeule.snapadmin.external.annotations.DisableEditField; import space.mori.dalbodeule.snapadmin.external.annotations.DisableExport; import space.mori.dalbodeule.snapadmin.external.annotations.HiddenColumn; import space.mori.dalbodeule.snapadmin.external.dbmapping.fields.DbField; @@ -351,7 +351,7 @@ public class DbObjectSchema { } public boolean isEditEnabled() { - return entityClass.getAnnotation(DisableEdit.class) == null; + return entityClass.getAnnotation(DisableEditField.class) == null; } public boolean isCreateEnabled() { diff --git a/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/fields/DbField.java b/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/fields/DbField.java index 7f00c28..986bb46 100644 --- a/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/fields/DbField.java +++ b/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/fields/DbField.java @@ -87,6 +87,8 @@ public class DbField { * annotation has been applied. */ private String format; + + private boolean disableEditField = false; /** * The schema this field belongs to @@ -94,13 +96,14 @@ public class DbField { @JsonIgnore private DbObjectSchema schema; - public DbField(String javaName, String name, Field field, DbFieldType type, DbObjectSchema schema, String format) { + public DbField(String javaName, String name, Field field, DbFieldType type, DbObjectSchema schema, String format, boolean isDisable) { this.javaName = javaName; this.dbName = name; this.schema = schema; this.field = field; this.type = type; this.format = format; + this.disableEditField = isDisable; } public String getJavaName() { @@ -189,6 +192,14 @@ public class DbField { public boolean isText() { return type instanceof TextFieldType; } + + public boolean isDisableEditField() { + return disableEditField; + } + + public void setDisableEditField(boolean managed) { + this.disableEditField = managed; + } /** * Returns the value to use in the "step" HTML attribute diff --git a/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/fields/InstantFieldType.java b/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/fields/InstantFieldType.java index 8782983..ad6c2f9 100644 --- a/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/fields/InstantFieldType.java +++ b/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/fields/InstantFieldType.java @@ -24,26 +24,116 @@ import java.time.ZoneOffset; import java.util.List; import space.mori.dalbodeule.snapadmin.external.dto.CompareOperator; +import java.time.*; +import java.time.format.*; +import java.time.temporal.TemporalAccessor; +import java.util.Date; public class InstantFieldType extends DbFieldType { - @Override - public String getFragmentName() { - return "datetime"; - } + // 다양한 날짜/시간 형식을 처리할 수 있는 포맷터들 + private static final DateTimeFormatter[] FORMATTERS = { + DateTimeFormatter.ISO_INSTANT, + DateTimeFormatter.ISO_DATE_TIME, + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"), + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"), + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") + }; - @Override - public Object parseValue(Object value) { - if (value == null || value.toString().isBlank()) return null; - return LocalDateTime.parse(value.toString()).toInstant(ZoneOffset.UTC); - } + @Override + public String getFragmentName() { + return "datetime"; + } - @Override - public Class getJavaClass() { - return Instant.class; - } - - @Override - public List getCompareOperators() { - return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE); - } + @Override + public Object parseValue(Object value) { + if (value == null) return null; + + // 이미 Instant 타입인 경우 + if (value instanceof Instant) { + return value; + } + + // LocalDateTime에서 Instant로 변환 + if (value instanceof LocalDateTime) { + return ((LocalDateTime) value) + .atZone(ZoneId.systemDefault()) + .toInstant(); + } + + // Date에서 Instant로 변환 + if (value instanceof Date) { + return ((Date) value).toInstant(); + } + + // LocalDate에서 Instant로 변환 + if (value instanceof LocalDate) { + return ((LocalDate) value) + .atStartOfDay(ZoneId.systemDefault()) + .toInstant(); + } + + // 문자열 처리 + String stringValue = value.toString(); + if (stringValue.isBlank()) return null; + + stringValue = stringValue.trim(); + + // 직접 Instant 파싱 시도 + try { + return Instant.parse(stringValue); + } catch (DateTimeParseException e) { + // 실패, 다른 방법으로 시도 + } + + // 다른 날짜/시간 형식을 통해 변환 시도 + for (DateTimeFormatter formatter : FORMATTERS) { + try { + // ISO_INSTANT의 경우 Instant.parse()와 동일하므로 스킵 + if (formatter == DateTimeFormatter.ISO_INSTANT) { + continue; + } + + // ISO_DATE_TIME 형식은 ZoneId가 필요 + if (formatter == DateTimeFormatter.ISO_DATE_TIME) { + return ZonedDateTime.parse(stringValue, formatter).toInstant(); + } + + // Z로 끝나는 형식은 UTC 시간으로 파싱 + if (stringValue.endsWith("Z")) { + return ZonedDateTime.parse(stringValue, formatter.withZone(ZoneOffset.UTC)) + .toInstant(); + } + + // 기타 형식은 시스템 기본 시간대 사용 + TemporalAccessor ta = formatter.parse(stringValue); + return LocalDateTime.from(ta) + .atZone(ZoneId.systemDefault()) + .toInstant(); + } catch (DateTimeParseException e) { + // 다음 형식 시도 + continue; + } + } + + // 밀리초 타임스탬프로 시도 + try { + long timestamp = Long.parseLong(stringValue); + return Instant.ofEpochMilli(timestamp); + } catch (NumberFormatException e) { + // 숫자 파싱 실패, 무시 + } + + // 모든 파싱 시도 실패 + return null; + } + + @Override + public Class getJavaClass() { + return Instant.class; + } + + @Override + public List getCompareOperators() { + return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE); + } } \ No newline at end of file diff --git a/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/fields/LocalDateTimeFieldType.java b/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/fields/LocalDateTimeFieldType.java index 4ab07a6..0d6decd 100644 --- a/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/fields/LocalDateTimeFieldType.java +++ b/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/fields/LocalDateTimeFieldType.java @@ -18,30 +18,65 @@ package space.mori.dalbodeule.snapadmin.external.dbmapping.fields; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.List; import space.mori.dalbodeule.snapadmin.external.dto.CompareOperator; public class LocalDateTimeFieldType extends DbFieldType { - @Override - public String getFragmentName() { - return "datetime"; - } + private static final DateTimeFormatter[] FORMATTERS = { + DateTimeFormatter.ISO_LOCAL_DATE_TIME, + DateTimeFormatter.ISO_DATE_TIME, + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"), + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"), + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS"), + DateTimeFormatter.ofPattern("yyyy-MM-dd") + }; - @Override - public Object parseValue(Object value) { - if (value == null || value.toString().isBlank()) return null; - return LocalDateTime.parse(value.toString()); - } + @Override + public String getFragmentName() { + return "datetime"; + } - @Override - public Class getJavaClass() { - return LocalDateTime.class; - } - - @Override - public List getCompareOperators() { - return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE); - } + @Override + public Object parseValue(Object value) { + if (value == null || value.toString().isBlank()) return null; + + // 이미 LocalDateTime 객체인 경우 + if (value instanceof LocalDateTime) { + return value; + } + + String stringValue = value.toString().trim(); + + // 여러 형식으로 파싱 시도 + for (DateTimeFormatter formatter : FORMATTERS) { + try { + // 날짜만 있는 형식인 경우 시간을 00:00:00으로 설정 + if (formatter.equals(DateTimeFormatter.ofPattern("yyyy-MM-dd"))) { + return LocalDate.parse(stringValue, formatter).atStartOfDay(); + } + return LocalDateTime.parse(stringValue, formatter); + } catch (DateTimeParseException e) { + // 이 형식으로 파싱 실패, 다음 형식 시도 + continue; + } + } + + // 모든 형식 파싱 실패 시 예외 발생 + throw new IllegalArgumentException("날짜/시간 형식을 파싱할 수 없습니다: " + stringValue); + } + + @Override + public Class getJavaClass() { + return LocalDateTime.class; + } + + @Override + public List getCompareOperators() { + return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE); + } } \ No newline at end of file diff --git a/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/fields/OffsetDateTimeFieldType.java b/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/fields/OffsetDateTimeFieldType.java index f714fe7..26d07a0 100644 --- a/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/fields/OffsetDateTimeFieldType.java +++ b/src/main/java/space/mori/dalbodeule/snapadmin/external/dbmapping/fields/OffsetDateTimeFieldType.java @@ -18,12 +18,23 @@ package space.mori.dalbodeule.snapadmin.external.dbmapping.fields; -import java.time.OffsetDateTime; +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.List; import space.mori.dalbodeule.snapadmin.external.dto.CompareOperator; public class OffsetDateTimeFieldType extends DbFieldType { + // 다양한 날짜/시간 형식을 처리할 수 있는 포맷터들 + private static final DateTimeFormatter[] FORMATTERS = { + DateTimeFormatter.ISO_OFFSET_DATE_TIME, + DateTimeFormatter.ISO_ZONED_DATE_TIME, + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX"), + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"), + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssXXX") + }; + @Override public String getFragmentName() { return "datetime"; @@ -31,8 +42,110 @@ public class OffsetDateTimeFieldType extends DbFieldType { @Override public Object parseValue(Object value) { - if (value == null || value.toString().isBlank()) return null; - return OffsetDateTime.parse(value.toString()); + if (value == null) return null; + + // 이미 OffsetDateTime 타입인 경우 + if (value instanceof OffsetDateTime) { + return value; + } + + // ZonedDateTime에서 OffsetDateTime으로 변환 + if (value instanceof ZonedDateTime) { + return ((ZonedDateTime) value).toOffsetDateTime(); + } + + // LocalDateTime에서 OffsetDateTime으로 변환 (시스템 기본 오프셋 사용) + if (value instanceof LocalDateTime) { + return ((LocalDateTime) value) + .atZone(ZoneId.systemDefault()) + .toOffsetDateTime(); + } + + // Instant에서 OffsetDateTime으로 변환 + if (value instanceof Instant) { + return ((Instant) value) + .atZone(ZoneId.systemDefault()) + .toOffsetDateTime(); + } + + // LocalDate에서 OffsetDateTime으로 변환 + if (value instanceof LocalDate) { + return ((LocalDate) value) + .atStartOfDay() + .atZone(ZoneId.systemDefault()) + .toOffsetDateTime(); + } + + // 문자열 처리 + String stringValue = value.toString(); + if (stringValue.isBlank()) return null; + + stringValue = stringValue.trim(); + + // 직접 OffsetDateTime 파싱 시도 + try { + return OffsetDateTime.parse(stringValue); + } catch (DateTimeParseException e) { + // 실패, 다른 방법으로 시도 + } + + // 여러 가지 형식으로 파싱 시도 + for (DateTimeFormatter formatter : FORMATTERS) { + try { + // ISO_OFFSET_DATE_TIME은 이미 위에서 시도했으므로 스킵 + if (formatter == DateTimeFormatter.ISO_OFFSET_DATE_TIME) { + continue; + } + + if (formatter == DateTimeFormatter.ISO_ZONED_DATE_TIME) { + return ZonedDateTime.parse(stringValue, formatter).toOffsetDateTime(); + } + + return OffsetDateTime.parse(stringValue, formatter); + } catch (DateTimeParseException e) { + // 다음 형식 시도 + continue; + } + } + + // ISO 날짜/시간 형식에 시스템 기본 오프셋 추가 시도 + try { + LocalDateTime ldt = LocalDateTime.parse(stringValue); + return ldt.atZone(ZoneId.systemDefault()).toOffsetDateTime(); + } catch (DateTimeParseException e) { + // 실패, 다음 방법 시도 + } + + // 날짜만 있는 경우 (UTC 자정으로 처리) + try { + LocalDate date = LocalDate.parse(stringValue); + return date.atStartOfDay().atZone(ZoneId.systemDefault()).toOffsetDateTime(); + } catch (DateTimeParseException e) { + // 날짜 파싱 실패, 무시 + } + + // 밀리초 타임스탬프로 시도 + try { + long timestamp = Long.parseLong(stringValue); + return Instant.ofEpochMilli(timestamp) + .atZone(ZoneId.systemDefault()) + .toOffsetDateTime(); + } catch (NumberFormatException e) { + // 숫자 파싱 실패, 무시 + } + + // Z로 끝나는 UTC 시간 문자열 처리 시도 + if (stringValue.endsWith("Z")) { + try { + Instant instant = Instant.parse(stringValue); + return instant.atZone(ZoneId.systemDefault()).toOffsetDateTime(); + } catch (DateTimeParseException e) { + // 실패, 무시 + } + } + + // 모든 파싱 시도 실패 + return null; } @Override @@ -44,4 +157,4 @@ public class OffsetDateTimeFieldType extends DbFieldType { public List getCompareOperators() { return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE); } -} +} \ No newline at end of file diff --git a/src/main/java/space/mori/dalbodeule/snapadmin/internal/service/ConsoleQueryService.java b/src/main/java/space/mori/dalbodeule/snapadmin/internal/service/ConsoleQueryService.java index d15eb35..5a46e64 100644 --- a/src/main/java/space/mori/dalbodeule/snapadmin/internal/service/ConsoleQueryService.java +++ b/src/main/java/space/mori/dalbodeule/snapadmin/internal/service/ConsoleQueryService.java @@ -20,6 +20,7 @@ package space.mori.dalbodeule.snapadmin.internal.service; import java.util.List; import java.util.Optional; +import java.util.logging.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -35,11 +36,18 @@ public class ConsoleQueryService { @Autowired private ConsoleQueryRepository repo; - + + private final Logger logger = Logger.getLogger(ConsoleQueryService.class.getName()); + public ConsoleQuery save(ConsoleQuery q) { - return internalTransactionTemplate.execute((status) -> { - return repo.save(q); - }); + try { + return internalTransactionTemplate.execute((status) -> { + return repo.save(q); + }); + } catch(Exception e) { + logger.severe("Error while saving console query: " + e.getMessage()); + throw e; + } } public void delete(String id) { diff --git a/src/main/resources/templates/snapadmin/model/create.html b/src/main/resources/templates/snapadmin/model/create.html index b3827c4..4c5579c 100644 --- a/src/main/resources/templates/snapadmin/model/create.html +++ b/src/main/resources/templates/snapadmin/model/create.html @@ -31,8 +31,8 @@
+ th:unless="${(field.isGeneratedValue() && create) || field.isDisableEditField()}" + th:classAppend="|${validationErrors != null && validationErrors.hasErrors(field.getJavaName()) ? 'invalid' : ''}|">
-
+

[[ ${field.getJavaName()} ]]