diff --git a/pom.xml b/pom.xml
index 0ddb51f..6dae032 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,10 +13,17 @@
spring-boot-db-admin
0.1.2
spring-boot-db-admin
- Srping Boot DB Admin Dashboard
+ Srping Boot Database Admin is an auto-generated CRUD admin panel for Spring Boot apps
17
+
+
+ GPL-v3.0
+ http://www.gnu.org/licenses/gpl-3.0.txt
+
+
+
release
@@ -38,8 +45,8 @@
ALWAYS
https://s01.oss.sonatype.org/service/local
- false
- false
+ true
+ true
target/staging-deploy
diff --git a/src/main/java/tech/ailef/dbadmin/external/DbAdmin.java b/src/main/java/tech/ailef/dbadmin/external/DbAdmin.java
index bbb888e..b3eae7a 100644
--- a/src/main/java/tech/ailef/dbadmin/external/DbAdmin.java
+++ b/src/main/java/tech/ailef/dbadmin/external/DbAdmin.java
@@ -53,6 +53,12 @@ public class DbAdmin {
private String modelsPackage;
+ /**
+ * Builds the DbAdmin instance by scanning the `@Entity` beans and loading
+ * the schemas.
+ * @param entityManager the entity manager
+ * @param properties the configuration properties
+ */
public DbAdmin(@Autowired EntityManager entityManager, @Autowired DbAdminProperties properties) {
this.modelsPackage = properties.getModelsPackage();
this.entityManager = entityManager;
@@ -71,7 +77,7 @@ public class DbAdmin {
/**
* Returns all the loaded schemas (i.e. entity classes)
- * @return
+ * @return the list of loaded schemas from the `@Entity` classes
*/
public List getSchemas() {
return Collections.unmodifiableList(schemas);
@@ -80,7 +86,7 @@ public class DbAdmin {
/**
* Finds a schema by its full class name
* @param className qualified class name
- * @return
+ * @return the schema with this class name
* @throws DbAdminException if corresponding schema not found
*/
public DbObjectSchema findSchemaByClassName(String className) {
@@ -92,7 +98,7 @@ public class DbAdmin {
/**
* Finds a schema by its table name
* @param tableName the table name on the database
- * @return
+ * @return the schema with this table name
* @throws DbAdminException if corresponding schema not found
*/
public DbObjectSchema findSchemaByTableName(String tableName) {
@@ -102,9 +108,9 @@ public class DbAdmin {
}
/**
- * Finds a schema by its class
- * @param klass
- * @return
+ * Finds a schema by its class object
+ * @param the `@Entity` class you want to find the schema for
+ * @return the schema for the `@Entity` class
* @throws DbAdminException if corresponding schema not found
*/
public DbObjectSchema findSchemaByClass(Class> klass) {
@@ -118,7 +124,7 @@ public class DbAdmin {
*
* If any field is not mappable, the method will throw an exception.
* @param bd
- * @return
+ * @return a schema derived from the `@Entity` class
*/
private DbObjectSchema processBeanDefinition(BeanDefinition bd) {
String fullClassName = bd.getBeanClassName();
diff --git a/src/main/java/tech/ailef/dbadmin/external/DbAdminAutoConfiguration.java b/src/main/java/tech/ailef/dbadmin/external/DbAdminAutoConfiguration.java
index 7f11371..8899a20 100644
--- a/src/main/java/tech/ailef/dbadmin/external/DbAdminAutoConfiguration.java
+++ b/src/main/java/tech/ailef/dbadmin/external/DbAdminAutoConfiguration.java
@@ -21,6 +21,10 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
import tech.ailef.dbadmin.internal.InternalDbAdminConfiguration;
+/**
+ * The configuration class that adds and configures the "internal" data source.
+ *
+ */
@ConditionalOnProperty(name = "dbadmin.enabled", matchIfMissing = true)
@ComponentScan
@EnableConfigurationProperties(DbAdminProperties.class)
@@ -56,7 +60,7 @@ public class DbAdminAutoConfiguration {
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(internalDataSource());
factoryBean.setPersistenceUnitName("internal");
- factoryBean.setPackagesToScan("tech.ailef.dbadmin.internal.model"); // , "tech.ailef.dbadmin.repository");
+ factoryBean.setPackagesToScan("tech.ailef.dbadmin.internal.model");
factoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
diff --git a/src/main/java/tech/ailef/dbadmin/external/DbAdminProperties.java b/src/main/java/tech/ailef/dbadmin/external/DbAdminProperties.java
index c2e3544..5dd458b 100644
--- a/src/main/java/tech/ailef/dbadmin/external/DbAdminProperties.java
+++ b/src/main/java/tech/ailef/dbadmin/external/DbAdminProperties.java
@@ -26,6 +26,9 @@ public class DbAdminProperties {
*/
private String modelsPackage;
+ /**
+ * Set to true when running the tests to configure the "internal" data source as in memory
+ */
private boolean testMode = false;
public boolean isEnabled() {
diff --git a/src/main/java/tech/ailef/dbadmin/external/annotations/DisplayFormat.java b/src/main/java/tech/ailef/dbadmin/external/annotations/DisplayFormat.java
index 7e7641e..7e02b04 100644
--- a/src/main/java/tech/ailef/dbadmin/external/annotations/DisplayFormat.java
+++ b/src/main/java/tech/ailef/dbadmin/external/annotations/DisplayFormat.java
@@ -13,5 +13,9 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DisplayFormat {
+ /**
+ * The format to apply to the field's value
+ * @return
+ */
public String format() default "";
}
\ No newline at end of file
diff --git a/src/main/java/tech/ailef/dbadmin/external/annotations/Filterable.java b/src/main/java/tech/ailef/dbadmin/external/annotations/Filterable.java
index cbcf71e..4d90bf0 100644
--- a/src/main/java/tech/ailef/dbadmin/external/annotations/Filterable.java
+++ b/src/main/java/tech/ailef/dbadmin/external/annotations/Filterable.java
@@ -16,5 +16,9 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Filterable {
+ /**
+ * The type of filter (DEFAULT or CATEGORICAL)
+ * @return
+ */
public FilterableType type() default FilterableType.DEFAULT;
}
\ No newline at end of file
diff --git a/src/main/java/tech/ailef/dbadmin/external/annotations/FilterableType.java b/src/main/java/tech/ailef/dbadmin/external/annotations/FilterableType.java
index 79cf09b..e4a91b1 100644
--- a/src/main/java/tech/ailef/dbadmin/external/annotations/FilterableType.java
+++ b/src/main/java/tech/ailef/dbadmin/external/annotations/FilterableType.java
@@ -1,5 +1,23 @@
package tech.ailef.dbadmin.external.annotations;
+/**
+ * Type of filters that can be used in the faceted search.
+ *
+ */
public enum FilterableType {
- DEFAULT, CATEGORICAL;
+ /**
+ * The default filter provides a list of standard operators
+ * customized to the field type (e.g. greater than/less than/equals for numbers,
+ * after/before/equals for dates, contains/equals for strings, etc...), with,
+ * if applicable, an autocomplete form if the field references a foreign key.
+ */
+ DEFAULT,
+ /**
+ * The categorical filter provides the full list of possible values
+ * for the field, rendered as a list of clickable items (that will
+ * filter for equality). This provides a better UX if the field can take
+ * a limited number of values and it's more convenient to have them all
+ * on screen rather than typing them.
+ */
+ CATEGORICAL;
}
diff --git a/src/main/java/tech/ailef/dbadmin/external/controller/DownloadController.java b/src/main/java/tech/ailef/dbadmin/external/controller/DownloadController.java
index 36c8863..f46edce 100644
--- a/src/main/java/tech/ailef/dbadmin/external/controller/DownloadController.java
+++ b/src/main/java/tech/ailef/dbadmin/external/controller/DownloadController.java
@@ -37,6 +37,13 @@ public class DownloadController {
private DbAdmin dbAdmin;
+ /**
+ * Serve a binary field as an image
+ * @param className
+ * @param fieldName
+ * @param id
+ * @return
+ */
@GetMapping(value="/{className}/{fieldName}/{id}/image", produces = MediaType.IMAGE_JPEG_VALUE)
@ResponseBody
public ResponseEntity serveImage(@PathVariable String className,
@@ -58,6 +65,16 @@ public class DownloadController {
}
+ /**
+ * Serve a binary field as a file. This tries to detect the file type using Tika
+ * in order to serve the file with a plausible extension, since we don't have
+ * any meta-data about what was originally uploaded and it is not feasible to
+ * store it (it could be modified on another end and we wouldn't be aware of it).
+ * @param className
+ * @param fieldName
+ * @param id
+ * @return
+ */
@GetMapping("/{className}/{fieldName}/{id}")
@ResponseBody
public ResponseEntity serveFile(@PathVariable String className,
diff --git a/src/main/java/tech/ailef/dbadmin/external/controller/GlobalController.java b/src/main/java/tech/ailef/dbadmin/external/controller/GlobalController.java
index 887eea6..8ba6162 100644
--- a/src/main/java/tech/ailef/dbadmin/external/controller/GlobalController.java
+++ b/src/main/java/tech/ailef/dbadmin/external/controller/GlobalController.java
@@ -43,11 +43,21 @@ public class GlobalController {
return props.getBaseUrl();
}
+ /**
+ * The full request URL, not including the query string
+ * @param request
+ * @return
+ */
@ModelAttribute("requestUrl")
public String getRequestUrl(HttpServletRequest request) {
return request.getRequestURI();
}
+ /**
+ * The UserConfiguration object used to retrieve values specified
+ * in the settings table.
+ * @return
+ */
@ModelAttribute("userConf")
public UserConfiguration getUserConf() {
return userConf;
diff --git a/src/main/java/tech/ailef/dbadmin/external/controller/rest/AutocompleteController.java b/src/main/java/tech/ailef/dbadmin/external/controller/rest/AutocompleteController.java
index 7f42dca..9817fa4 100644
--- a/src/main/java/tech/ailef/dbadmin/external/controller/rest/AutocompleteController.java
+++ b/src/main/java/tech/ailef/dbadmin/external/controller/rest/AutocompleteController.java
@@ -28,6 +28,12 @@ public class AutocompleteController {
@Autowired
private DbAdminRepository repository;
+ /**
+ * Returns a list of entities from a given table that match an input query.
+ * @param className
+ * @param query
+ * @return
+ */
@GetMapping("/{className}")
public ResponseEntity> autocomplete(@PathVariable String className, @RequestParam String query) {
DbObjectSchema schema = dbAdmin.findSchemaByClassName(className);
diff --git a/src/main/java/tech/ailef/dbadmin/external/dbmapping/CustomJpaRepository.java b/src/main/java/tech/ailef/dbadmin/external/dbmapping/CustomJpaRepository.java
index 702d725..90b32ef 100644
--- a/src/main/java/tech/ailef/dbadmin/external/dbmapping/CustomJpaRepository.java
+++ b/src/main/java/tech/ailef/dbadmin/external/dbmapping/CustomJpaRepository.java
@@ -80,6 +80,51 @@ public class CustomJpaRepository extends SimpleJpaRepository {
.setFirstResult((page - 1) * pageSize).getResultList();
}
+
+ @SuppressWarnings("unchecked")
+ public int update(DbObjectSchema schema, Map params, Map files) {
+ CriteriaBuilder cb = entityManager.getCriteriaBuilder();
+
+ CriteriaUpdate update = cb.createCriteriaUpdate(schema.getJavaClass());
+
+ Root root = update.from(schema.getJavaClass());
+
+ for (DbField field : schema.getSortedFields()) {
+ if (field.isPrimaryKey()) continue;
+
+ boolean keepValue = params.getOrDefault("__keep_" + field.getName(), "off").equals("on");
+ if (keepValue) continue;
+
+ String stringValue = params.get(field.getName());
+ Object value = null;
+ if (stringValue != null && stringValue.isBlank()) stringValue = null;
+ if (stringValue != null) {
+ value = field.getType().parseValue(stringValue);
+ } else {
+ try {
+ MultipartFile file = files.get(field.getName());
+ if (file != null) {
+ if (file.isEmpty()) value = null;
+ else value = file.getBytes();
+ }
+ } catch (IOException e) {
+ throw new DbAdminException(e);
+ }
+ }
+
+ if (field.getConnectedSchema() != null)
+ value = field.getConnectedSchema().getJpaRepository().findById(value).get();
+
+ update.set(root.get(field.getJavaName()), value);
+ }
+
+ String pkName = schema.getPrimaryKey().getJavaName();
+ update.where(cb.equal(root.get(pkName), params.get(schema.getPrimaryKey().getName())));
+
+ Query query = entityManager.createQuery(update);
+ return query.executeUpdate();
+ }
+
@SuppressWarnings("unchecked")
private List buildPredicates(String q, Set queryFilters,
CriteriaBuilder cb, Path root) {
@@ -155,48 +200,4 @@ public class CustomJpaRepository extends SimpleJpaRepository {
}
return finalPredicates;
}
-
- @SuppressWarnings("unchecked")
- public int update(DbObjectSchema schema, Map params, Map files) {
- CriteriaBuilder cb = entityManager.getCriteriaBuilder();
-
- CriteriaUpdate update = cb.createCriteriaUpdate(schema.getJavaClass());
-
- Root root = update.from(schema.getJavaClass());
-
- for (DbField field : schema.getSortedFields()) {
- if (field.isPrimaryKey()) continue;
-
- boolean keepValue = params.getOrDefault("__keep_" + field.getName(), "off").equals("on");
- if (keepValue) continue;
-
- String stringValue = params.get(field.getName());
- Object value = null;
- if (stringValue != null && stringValue.isBlank()) stringValue = null;
- if (stringValue != null) {
- value = field.getType().parseValue(stringValue);
- } else {
- try {
- MultipartFile file = files.get(field.getName());
- if (file != null) {
- if (file.isEmpty()) value = null;
- else value = file.getBytes();
- }
- } catch (IOException e) {
- throw new DbAdminException(e);
- }
- }
-
- if (field.getConnectedSchema() != null)
- value = field.getConnectedSchema().getJpaRepository().findById(value).get();
-
- update.set(root.get(field.getJavaName()), value);
- }
-
- String pkName = schema.getPrimaryKey().getJavaName();
- update.where(cb.equal(root.get(pkName), params.get(schema.getPrimaryKey().getName())));
-
- Query query = entityManager.createQuery(update);
- return query.executeUpdate();
- }
}
diff --git a/src/main/java/tech/ailef/dbadmin/external/dbmapping/DbField.java b/src/main/java/tech/ailef/dbadmin/external/dbmapping/DbField.java
index 6e5d4c5..2161958 100644
--- a/src/main/java/tech/ailef/dbadmin/external/dbmapping/DbField.java
+++ b/src/main/java/tech/ailef/dbadmin/external/dbmapping/DbField.java
@@ -13,13 +13,25 @@ import tech.ailef.dbadmin.external.annotations.Filterable;
import tech.ailef.dbadmin.external.annotations.FilterableType;
public class DbField {
+ /**
+ * The inferred name of this field on the database
+ */
protected String dbName;
+ /**
+ * The name of this field in the Java code (instance variable)
+ */
protected String javaName;
+ /**
+ * The type of this field
+ */
protected DbFieldType type;
@JsonIgnore
+ /**
+ * The primitive Field object from the Class
+ */
protected Field field;
/**
@@ -29,12 +41,25 @@ public class DbField {
@JsonIgnore
private Class> connectedType;
+ /**
+ * Whether this field is a primary key
+ */
private boolean primaryKey;
+ /**
+ * Whether this field is nullable
+ */
private boolean nullable;
+ /**
+ * The optional format to apply to this field, if the `@DisplayFormat`
+ * annotation has been applied.
+ */
private String format;
+ /**
+ * The schema this field belongs to
+ */
@JsonIgnore
private DbObjectSchema schema;
diff --git a/src/main/java/tech/ailef/dbadmin/external/dbmapping/DbFieldType.java b/src/main/java/tech/ailef/dbadmin/external/dbmapping/DbFieldType.java
index 8cf1634..3f4eaaf 100644
--- a/src/main/java/tech/ailef/dbadmin/external/dbmapping/DbFieldType.java
+++ b/src/main/java/tech/ailef/dbadmin/external/dbmapping/DbFieldType.java
@@ -14,6 +14,9 @@ import jakarta.persistence.OneToOne;
import tech.ailef.dbadmin.external.dto.CompareOperator;
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
+/**
+ * The list of supported field types
+ */
public enum DbFieldType {
INTEGER {
@Override
@@ -27,7 +30,6 @@ public enum DbFieldType {
}
@Override
-
public Class> getJavaClass() {
return Integer.class;
}
diff --git a/src/main/java/tech/ailef/dbadmin/external/dbmapping/DbFieldValue.java b/src/main/java/tech/ailef/dbadmin/external/dbmapping/DbFieldValue.java
index 6575818..bea6da3 100644
--- a/src/main/java/tech/ailef/dbadmin/external/dbmapping/DbFieldValue.java
+++ b/src/main/java/tech/ailef/dbadmin/external/dbmapping/DbFieldValue.java
@@ -4,6 +4,10 @@ import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonIgnore;
+/**
+ * Wrapper for the value of a field
+ *
+ */
public class DbFieldValue {
private Object value;
diff --git a/src/main/java/tech/ailef/dbadmin/external/dbmapping/DbObject.java b/src/main/java/tech/ailef/dbadmin/external/dbmapping/DbObject.java
index 0da11bc..42021b9 100644
--- a/src/main/java/tech/ailef/dbadmin/external/dbmapping/DbObject.java
+++ b/src/main/java/tech/ailef/dbadmin/external/dbmapping/DbObject.java
@@ -15,9 +15,19 @@ import tech.ailef.dbadmin.external.annotations.DisplayName;
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
import tech.ailef.dbadmin.external.misc.Utils;
+/**
+ * Wrapper for all objects retrieved from the database.
+ *
+ */
public class DbObject {
+ /**
+ * The instance of the object, i.e. an instance of the `@Entity` class
+ */
private Object instance;
+ /**
+ * The schema this object belongs to
+ */
private DbObjectSchema schema;
public DbObject(Object instance, DbObjectSchema schema) {
diff --git a/src/main/java/tech/ailef/dbadmin/external/dbmapping/DbObjectSchema.java b/src/main/java/tech/ailef/dbadmin/external/dbmapping/DbObjectSchema.java
index 6b389bd..2ed5929 100644
--- a/src/main/java/tech/ailef/dbadmin/external/dbmapping/DbObjectSchema.java
+++ b/src/main/java/tech/ailef/dbadmin/external/dbmapping/DbObjectSchema.java
@@ -22,6 +22,11 @@ import tech.ailef.dbadmin.external.annotations.ComputedColumn;
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
import tech.ailef.dbadmin.external.misc.Utils;
+/**
+ * A class that represents a table/`@Entity` as reconstructed from the
+ * JPA annotations found on its fields.
+ *
+ */
public class DbObjectSchema {
/**
* All the fields in this table. The fields include all the
diff --git a/src/main/java/tech/ailef/dbadmin/external/dto/AutocompleteSearchResult.java b/src/main/java/tech/ailef/dbadmin/external/dto/AutocompleteSearchResult.java
index a714bd0..7701972 100644
--- a/src/main/java/tech/ailef/dbadmin/external/dto/AutocompleteSearchResult.java
+++ b/src/main/java/tech/ailef/dbadmin/external/dto/AutocompleteSearchResult.java
@@ -2,6 +2,11 @@ package tech.ailef.dbadmin.external.dto;
import tech.ailef.dbadmin.external.dbmapping.DbObject;
+/**
+ * An object to hold autocomplete results returned from the
+ * respective AutocompleteController
+ *
+ */
public class AutocompleteSearchResult {
private Object id;
diff --git a/src/main/java/tech/ailef/dbadmin/external/dto/CompareOperator.java b/src/main/java/tech/ailef/dbadmin/external/dto/CompareOperator.java
index fbeb373..bc75990 100644
--- a/src/main/java/tech/ailef/dbadmin/external/dto/CompareOperator.java
+++ b/src/main/java/tech/ailef/dbadmin/external/dto/CompareOperator.java
@@ -1,5 +1,9 @@
package tech.ailef.dbadmin.external.dto;
+/**
+ * A list of operators that are used in faceted search.
+ *
+ */
public enum CompareOperator {
GT {
@Override
diff --git a/src/main/java/tech/ailef/dbadmin/external/dto/LogsSearchRequest.java b/src/main/java/tech/ailef/dbadmin/external/dto/LogsSearchRequest.java
index e766731..3d66a3a 100644
--- a/src/main/java/tech/ailef/dbadmin/external/dto/LogsSearchRequest.java
+++ b/src/main/java/tech/ailef/dbadmin/external/dto/LogsSearchRequest.java
@@ -3,19 +3,45 @@ package tech.ailef.dbadmin.external.dto;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
+/**
+ * A client request for the Action logs page where
+ * several filtering parameters are present
+ *
+ */
public class LogsSearchRequest {
+ /**
+ * The table name to filter on
+ */
private String table;
+ /**
+ * The action type to filter on (EDIT, CREATE, DELETE, ANY)
+ */
private String actionType;
+ /**
+ * The item id to filter on.
+ */
private String itemId;
+ /**
+ * The requested page
+ */
private int page;
+ /**
+ * The requested page size
+ */
private int pageSize;
+ /**
+ * The requested sort key
+ */
private String sortKey;
+ /**
+ * The requested sort order
+ */
private String sortOrder;
public String getTable() {
@@ -80,6 +106,10 @@ public class LogsSearchRequest {
+ page + ", pageSize=" + pageSize + ", sortKey=" + sortKey + ", sortOrder=" + sortOrder + "]";
}
+ /**
+ * Build a Spring PageRequest object from the parameters in this request
+ * @return a Spring PageRequest object
+ */
public PageRequest toPageRequest() {
int actualPage = page - 1 < 0 ? 0 : page - 1;
int actualPageSize = pageSize <= 0 ? 50 : pageSize;
diff --git a/src/main/java/tech/ailef/dbadmin/external/dto/PaginatedResult.java b/src/main/java/tech/ailef/dbadmin/external/dto/PaginatedResult.java
index 2b22fda..d389fab 100644
--- a/src/main/java/tech/ailef/dbadmin/external/dto/PaginatedResult.java
+++ b/src/main/java/tech/ailef/dbadmin/external/dto/PaginatedResult.java
@@ -2,9 +2,19 @@ package tech.ailef.dbadmin.external.dto;
import java.util.List;
+/**
+ * A wrapper class that holds info about the current pagination and one page
+ * of returned result.
+ */
public class PaginatedResult {
+ /**
+ * The pagination settings used to produce this output
+ */
private PaginationInfo pagination;
+ /**
+ * The list of results in the current page
+ */
private List results;
public PaginatedResult(PaginationInfo pagination, List page) {
diff --git a/src/main/java/tech/ailef/dbadmin/external/dto/QueryFilter.java b/src/main/java/tech/ailef/dbadmin/external/dto/QueryFilter.java
index 9449516..1f252c6 100644
--- a/src/main/java/tech/ailef/dbadmin/external/dto/QueryFilter.java
+++ b/src/main/java/tech/ailef/dbadmin/external/dto/QueryFilter.java
@@ -29,6 +29,10 @@ public class QueryFilter {
return value;
}
+ /**
+ * Provides a readable version of this query filter, customized
+ * based on field type and/or operator.
+ */
@Override
public String toString() {
if (value != null && !value.toString().isBlank()) {