This commit is contained in:
Francesco
2023-09-20 12:06:35 +02:00
parent c0801cc69d
commit 774c862ab3
25 changed files with 858 additions and 192 deletions

View File

@@ -95,7 +95,6 @@ public class DbAdmin {
field.setSchema(schema);
schema.addField(field);
System.out.println(field);
}
return schema;

View File

@@ -0,0 +1,11 @@
package tech.ailef.dbadmin.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 Filterable {
}

View File

@@ -5,6 +5,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
@@ -12,6 +13,7 @@ import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -22,12 +24,17 @@ import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import tech.ailef.dbadmin.DbAdmin;
import tech.ailef.dbadmin.dbmapping.DbAdminRepository;
import tech.ailef.dbadmin.dbmapping.DbObject;
import tech.ailef.dbadmin.dbmapping.DbObjectSchema;
import tech.ailef.dbadmin.dto.CompareOperator;
import tech.ailef.dbadmin.dto.PaginatedResult;
import tech.ailef.dbadmin.dto.QueryFilter;
import tech.ailef.dbadmin.exceptions.InvalidPageException;
import tech.ailef.dbadmin.misc.Utils;
@Controller
@RequestMapping("/dbadmin")
@@ -45,10 +52,15 @@ import tech.ailef.dbadmin.exceptions.InvalidPageException;
* - Pagination in one to many results?
* - BLOB upload (WIP: check edit not working)
* - AI console (PRO)
* - Action logs
* - Boolean icons
* - @Filterable
* - Boolean in create/edit is checkbox
* - SQL console (PRO)
* - JPA Validation (PRO)
* - Logging
* - ERROR 500: http://localhost:8080/dbadmin/model/tech.ailef.dbadmin.test.models.Order?query=2021
* - TODO FIX: list model page crash
* EDIT error on table product
* - Logs in web ui
* - Tests: AutocompleteController, REST API, create/edit
*/
@@ -86,16 +98,55 @@ public class DefaultDbAdminController {
public String list(Model model, @PathVariable String className,
@RequestParam(required=false) Integer page, @RequestParam(required=false) String query,
@RequestParam(required=false) Integer pageSize, @RequestParam(required=false) String sortKey,
@RequestParam(required=false) String sortOrder) {
@RequestParam(required=false) String sortOrder, @RequestParam MultiValueMap<String, String> otherParams,
HttpServletRequest request,
HttpServletResponse response) {
if (page == null) page = 1;
if (pageSize == null) pageSize = 50;
Set<QueryFilter> queryFilters = Utils.computeFilters(otherParams);
if (otherParams.containsKey("remove_field")) {
List<String> fields = otherParams.get("remove_field");
for (int i = 0; i < fields.size(); i++) {
QueryFilter toRemove =
new QueryFilter(
fields.get(i),
CompareOperator.valueOf(otherParams.get("remove_op").get(i).toUpperCase()),
otherParams.get("remove_value").get(i)
);
queryFilters.removeIf(f -> f.equals(toRemove));
}
MultiValueMap<String, String> parameterMap = Utils.computeParams(queryFilters);
MultiValueMap<String, String> filteredParams = new LinkedMultiValueMap<>();
request.getParameterMap().entrySet().stream()
.filter(e -> !e.getKey().startsWith("remove_") && !e.getKey().startsWith("filter_"))
.forEach(e -> {
filteredParams.putIfAbsent(e.getKey(), new ArrayList<>());
for (String v : e.getValue()) {
if (filteredParams.get(e.getKey()).isEmpty()) {
filteredParams.get(e.getKey()).add(v);
} else {
filteredParams.get(e.getKey()).set(0, v);
}
}
});
filteredParams.putAll(parameterMap);
String queryString = Utils.getQueryString(filteredParams);
String redirectUrl = request.getServletPath() + queryString;
return "redirect:" + redirectUrl.trim();
}
DbObjectSchema schema = dbAdmin.findSchemaByClassName(className);
try {
PaginatedResult result = null;
if (query != null) {
result = repository.search(schema, query, page, pageSize, sortKey, sortOrder);
if (query != null || !otherParams.isEmpty()) {
result = repository.search(schema, query, page, pageSize, sortKey, sortOrder, queryFilters);
} else {
result = repository.findAll(schema, page, pageSize, sortKey, sortOrder);
}
@@ -107,6 +158,7 @@ public class DefaultDbAdminController {
model.addAttribute("sortKey", sortKey);
model.addAttribute("query", query);
model.addAttribute("sortOrder", sortOrder);
model.addAttribute("activeFilters", queryFilters);
return "model/list";
} catch (InvalidPageException e) {
@@ -333,9 +385,12 @@ public class DefaultDbAdminController {
}
}
@GetMapping("/settings")
public String settings(Model model) {
model.addAttribute("activePage", "settings");
return "settings";
}
}

View File

@@ -0,0 +1,27 @@
package tech.ailef.dbadmin.controller;
import java.util.Map;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
import jakarta.servlet.http.HttpServletRequest;
/**
* This class registers some ModelAttribute objects that are
* used in all templates.
*/
@ControllerAdvice
public class GlobalController {
/**
* A multi valued map containing the query parameters. It is used primarily
* in building complex URL when performing faceted search with multiple filters.
* @param request the incoming request
* @return multi valued map of request parameters
*/
@ModelAttribute("queryParams")
public Map<String, String[]> getQueryParams(HttpServletRequest request) {
return request.getParameterMap();
}
}

View File

@@ -1,17 +1,29 @@
package tech.ailef.dbadmin.dbmapping;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.web.multipart.MultipartFile;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.CriteriaUpdate;
import jakarta.persistence.criteria.Path;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import tech.ailef.dbadmin.dto.CompareOperator;
import tech.ailef.dbadmin.dto.QueryFilter;
import tech.ailef.dbadmin.exceptions.DbAdminException;
@SuppressWarnings("rawtypes")
public class AdvancedJpaRepository extends SimpleJpaRepository {
@@ -27,51 +39,151 @@ public class AdvancedJpaRepository extends SimpleJpaRepository {
this.schema = schema;
}
public long count(String q) {
@SuppressWarnings("unchecked")
public long count(String q, Set<QueryFilter> queryFilters) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery query = cb.createQuery(Long.class);
Root root = query.from(schema.getJavaClass());
List<DbField> stringFields =
schema.getSortedFields().stream().filter(f -> f.getType() == DbFieldType.STRING)
.collect(Collectors.toList());
System.out.println("STRING F = " + stringFields);
List<Predicate> predicates = new ArrayList<>();
for (DbField f : stringFields) {
Path path = root.get(f.getJavaName());
predicates.add(cb.like(cb.lower(cb.toString(path)), "%" + q.toLowerCase() + "%"));
}
List<Predicate> finalPredicates = buildPredicates(q, queryFilters, cb, root);
query.select(cb.count(root.get(schema.getPrimaryKey().getName())))
.where(cb.or(predicates.toArray(new Predicate[predicates.size()])));
.where(
cb.and(
finalPredicates.toArray(new Predicate[finalPredicates.size()])
)
);
Object o = entityManager.createQuery(query).getSingleResult();
return (Long)o;
}
@SuppressWarnings("unchecked")
public List<Object> search(String q, int page, int pageSize, String sortKey, String sortOrder) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
public List<Object> search(String q, int page, int pageSize, String sortKey, String sortOrder, Set<QueryFilter> filters) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery query = cb.createQuery(schema.getJavaClass());
Root root = query.from(schema.getJavaClass());
List<DbField> stringFields =
schema.getSortedFields().stream().filter(f -> f.getType() == DbFieldType.STRING)
.collect(Collectors.toList());
List<Predicate> finalPredicates = buildPredicates(q, filters, cb, root);
List<Predicate> predicates = new ArrayList<>();
for (DbField f : stringFields) {
Path path = root.get(f.getJavaName());
predicates.add(cb.like(cb.lower(cb.toString(path)), "%" + q.toLowerCase() + "%"));
}
query.select(root)
.where(cb.or(predicates.toArray(new Predicate[predicates.size()])));
.where(
cb.and(
finalPredicates.toArray(new Predicate[finalPredicates.size()]) // query search on String fields
)
);
if (sortKey != null)
query.orderBy(sortOrder.equals("DESC") ? cb.desc(root.get(sortKey)) : cb.asc(root.get(sortKey)));
return entityManager.createQuery(query).setMaxResults(pageSize)
.setFirstResult((page - 1) * pageSize).getResultList();
}
private List<Predicate> buildPredicates(String q, Set<QueryFilter> queryFilters,
CriteriaBuilder cb, Path root) {
List<Predicate> finalPredicates = new ArrayList<>();
List<DbField> stringFields =
schema.getSortedFields().stream().filter(f -> f.getType() == DbFieldType.STRING)
.collect(Collectors.toList());
List<Predicate> queryPredicates = new ArrayList<>();
if (q != null) {
for (DbField f : stringFields) {
Path path = root.get(f.getJavaName());
queryPredicates.add(cb.like(cb.lower(cb.toString(path)), "%" + q.toLowerCase() + "%"));
}
Predicate queryPredicate = cb.or(queryPredicates.toArray(new Predicate[queryPredicates.size()]));
finalPredicates.add(queryPredicate);
}
if (queryFilters == null) queryFilters = new HashSet<>();
for (QueryFilter filter : queryFilters) {
CompareOperator op = filter.getOp();
String field = filter.getField();
String v = filter.getValue();
DbField dbField = schema.getFieldByJavaName(field);
Object value = dbField.getType().parseValue(v);
if (op == CompareOperator.STRING_EQ) {
finalPredicates.add(cb.equal(cb.lower(cb.toString(root.get(field))), value.toString().toLowerCase()));
} else if (op == CompareOperator.CONTAINS) {
finalPredicates.add(
cb.like(cb.lower(cb.toString(root.get(field))), "%" + value.toString().toLowerCase() + "%")
);
} else if (op == CompareOperator.EQ) {
finalPredicates.add(
cb.equal(root.get(field), value)
);
} else if (op == CompareOperator.GT) {
finalPredicates.add(
cb.greaterThan(root.get(field), value.toString())
);
} else if (op == CompareOperator.LT) {
finalPredicates.add(
cb.lessThan(root.get(field), value.toString())
);
} else if (op == CompareOperator.AFTER) {
if (value instanceof LocalDate)
finalPredicates.add(
cb.greaterThan(root.get(field), (LocalDate)value)
);
else if (value instanceof LocalDateTime)
finalPredicates.add(
cb.greaterThan(root.get(field), (LocalDateTime)value)
);
} else if (op == CompareOperator.BEFORE) {
if (value instanceof LocalDate)
finalPredicates.add(
cb.lessThan(root.get(field), (LocalDate)value)
);
else if (value instanceof LocalDateTime)
finalPredicates.add(
cb.lessThan(root.get(field), (LocalDateTime)value)
);
}
}
return finalPredicates;
}
public void update(DbObjectSchema schema, Map<String, String> params, Map<String, MultipartFile> files) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaUpdate update = cb.createCriteriaUpdate(schema.getJavaClass());
Root employee = update.from(schema.getJavaClass());
for (DbField field : schema.getSortedFields()) {
if (field.isPrimaryKey()) 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.getJavaName());
if (file != null)
value = file.getBytes();
} catch (IOException e) {
throw new DbAdminException(e);
}
}
update.set(employee.get(field.getJavaName()), value);
}
String pkName = schema.getPrimaryKey().getJavaName();
update.where(cb.equal(employee.get(pkName), params.get(schema.getPrimaryKey().getName())));
Query query = entityManager.createQuery(update);
int rowCount = query.executeUpdate();
}
}

View File

@@ -3,10 +3,12 @@ package tech.ailef.dbadmin.dbmapping;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.data.domain.Page;
@@ -21,6 +23,7 @@ import org.springframework.web.multipart.MultipartFile;
import jakarta.transaction.Transactional;
import tech.ailef.dbadmin.dto.PaginatedResult;
import tech.ailef.dbadmin.dto.PaginationInfo;
import tech.ailef.dbadmin.dto.QueryFilter;
import tech.ailef.dbadmin.exceptions.DbAdminException;
import tech.ailef.dbadmin.exceptions.InvalidPageException;
@@ -63,8 +66,8 @@ public class DbAdminRepository {
* @param query
* @return
*/
public long count(DbObjectSchema schema, String query) {
return schema.getJpaRepository().count(query);
public long count(DbObjectSchema schema, String query, Set<QueryFilter> queryFilters) {
return schema.getJpaRepository().count(query, queryFilters);
}
@@ -116,7 +119,7 @@ public class DbAdminRepository {
return new PaginatedResult(
new PaginationInfo(page, maxPage, pageSize, maxElement),
new PaginationInfo(page, maxPage, pageSize, maxElement, null, sortKey, sortOrder, new HashSet<>()),
results
);
}
@@ -126,14 +129,18 @@ public class DbAdminRepository {
* @param schema
* @param params
*/
@Transactional
public void update(DbObjectSchema schema, Map<String, String> params, Map<String, MultipartFile> files) {
Object[] updateArray = schema.getUpdateArray(params, files);
// Object[] updateArray = schema.getUpdateArray(params, files);
//
// String updateFields =
// schema.getSortedFields().stream().map(f -> "`" + f.getName() + "` = ?").collect(Collectors.joining(", "));
//
// String query = "UPDATE `" + schema.getTableName() + "` SET " + updateFields + " WHERE `" + schema.getPrimaryKey().getName() + "` = ?";
// jdbcTemplate.update(query, updateArray);
String updateFields =
schema.getSortedFields().stream().map(f -> "`" + f.getName() + "` = ?").collect(Collectors.joining(", "));
String query = "UPDATE `" + schema.getTableName() + "` SET " + updateFields + " WHERE `" + schema.getPrimaryKey().getName() + "` = ?";
jdbcTemplate.update(query, updateArray);
schema.getJpaRepository().update(schema, params, files);
}
@SuppressWarnings("unchecked")
@@ -203,17 +210,6 @@ public class DbAdminRepository {
insert.execute(allValues);
return primaryKey;
}
// String fieldsString =
// schema.getSortedFields().stream().skip(primaryKey == null ? 1 : 0).map(f -> "`" + f.getName() + "`").collect(Collectors.joining(", "));
//
// String placeholdersString =
// schema.getSortedFields().stream().skip(primaryKey == null ? 1 : 0).map(f -> "?").collect(Collectors.joining(", "));
// Object[] array = schema.getInsertArray(values, files);
//
// String query = "INSERT INTO " + schema.getTableName() + " (" + fieldsString + ") VALUES (" + placeholdersString + ");";
// jdbcTemplate.update(query, array);
// return primaryKey;
}
@@ -223,10 +219,11 @@ public class DbAdminRepository {
* @param query
* @return
*/
public PaginatedResult search(DbObjectSchema schema, String query, int page, int pageSize, String sortKey, String sortOrder) {
public PaginatedResult search(DbObjectSchema schema, String query, int page, int pageSize, String sortKey,
String sortOrder, Set<QueryFilter> queryFilters) {
AdvancedJpaRepository jpaRepository = schema.getJpaRepository();
long maxElement = count(schema, query);
long maxElement = count(schema, query, queryFilters);
int maxPage = (int)(Math.ceil ((double)maxElement / pageSize));
if (page <= 0) page = 1;
@@ -235,8 +232,8 @@ public class DbAdminRepository {
}
return new PaginatedResult(
new PaginationInfo(page, maxPage, pageSize, maxElement),
jpaRepository.search(query, page, pageSize, sortKey, sortOrder).stream()
new PaginationInfo(page, maxPage, pageSize, maxElement, query, sortKey, sortOrder, queryFilters),
jpaRepository.search(query, page, pageSize, sortKey, sortOrder, queryFilters).stream()
.map(o -> new DbObject(o, schema))
.toList()
);
@@ -251,7 +248,7 @@ public class DbAdminRepository {
public List<DbObject> search(DbObjectSchema schema, String query) {
AdvancedJpaRepository jpaRepository = schema.getJpaRepository();
return jpaRepository.search(query, 1, 50, null, null).stream()
return jpaRepository.search(query, 1, 50, null, null, null).stream()
.map(o -> new DbObject(o, schema))
.toList();
}

View File

@@ -4,12 +4,14 @@ import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import org.springframework.web.multipart.MultipartFile;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import tech.ailef.dbadmin.dto.CompareOperator;
import tech.ailef.dbadmin.exceptions.DbAdminException;
public enum DbFieldType {
@@ -29,6 +31,11 @@ public enum DbFieldType {
public Class<?> getJavaClass() {
return Integer.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
},
DOUBLE {
@Override
@@ -45,6 +52,11 @@ public enum DbFieldType {
public Class<?> getJavaClass() {
return Double.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
},
LONG {
@Override
@@ -61,6 +73,11 @@ public enum DbFieldType {
public Class<?> getJavaClass() {
return Long.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
},
FLOAT {
@Override
@@ -77,6 +94,11 @@ public enum DbFieldType {
public Class<?> getJavaClass() {
return Float.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
},
LOCAL_DATE {
@Override
@@ -93,6 +115,11 @@ public enum DbFieldType {
public Class<?> getJavaClass() {
return Float.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE);
}
},
LOCAL_DATE_TIME {
@Override
@@ -109,6 +136,11 @@ public enum DbFieldType {
public Class<?> getJavaClass() {
return LocalDateTime.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE);
}
},
STRING {
@Override
@@ -125,6 +157,11 @@ public enum DbFieldType {
public Class<?> getJavaClass() {
return String.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.CONTAINS, CompareOperator.STRING_EQ);
}
},
BOOLEAN {
@Override
@@ -141,6 +178,11 @@ public enum DbFieldType {
public Class<?> getJavaClass() {
return Boolean.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.EQ);
}
},
BIG_DECIMAL {
@Override
@@ -157,6 +199,11 @@ public enum DbFieldType {
public Class<?> getJavaClass() {
return BigDecimal.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
},
BYTE_ARRAY {
@Override
@@ -178,6 +225,10 @@ public enum DbFieldType {
return byte[].class;
}
@Override
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException("Binary fields are not comparable");
}
},
ONE_TO_MANY {
@Override
@@ -204,6 +255,11 @@ public enum DbFieldType {
public String toString() {
return "One to Many";
}
@Override
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException();
}
},
ONE_TO_ONE {
@Override
@@ -230,6 +286,11 @@ public enum DbFieldType {
public String toString() {
return "One to One";
}
@Override
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException();
}
},
MANY_TO_MANY {
@Override
@@ -256,6 +317,11 @@ public enum DbFieldType {
public String toString() {
return "Many to Many";
}
@Override
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException();
}
},
COMPUTED {
@Override
@@ -272,6 +338,11 @@ public enum DbFieldType {
public Class<?> getJavaClass() {
throw new UnsupportedOperationException();
}
@Override
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException();
}
};
public abstract String getHTMLName();
@@ -280,6 +351,8 @@ public enum DbFieldType {
public abstract Class<?> getJavaClass();
public abstract List<CompareOperator> getCompareOperators();
public boolean isRelationship() {
return false;
}

View File

@@ -17,8 +17,13 @@ public class DbFieldValue {
}
public String getFormattedValue() {
if (field.getFormat() == null) return value == null ? "NULL" : value.toString();
return String.format(field.getFormat(), value);
if (value == null) return null;
if (field.getFormat() == null) {
return value.toString();
} else {
return String.format(field.getFormat(), value);
}
}
public DbField getField() {

View File

@@ -199,8 +199,13 @@ public class DbObject {
String capitalize = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
Method[] methods = instance.getClass().getDeclaredMethods();
String prefix = "get";
if (schema.getFieldByJavaName(fieldName).getType() == DbFieldType.BOOLEAN) {
prefix = "is";
}
for (Method m : methods) {
if (m.getName().equals("get" + capitalize))
if (m.getName().equals(prefix + capitalize))
return m;
}

View File

@@ -10,8 +10,6 @@ import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.web.multipart.MultipartFile;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.ManyToMany;
@@ -19,6 +17,7 @@ import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import tech.ailef.dbadmin.DbAdmin;
import tech.ailef.dbadmin.annotations.ComputedColumn;
import tech.ailef.dbadmin.annotations.Filterable;
import tech.ailef.dbadmin.exceptions.DbAdminException;
import tech.ailef.dbadmin.misc.Utils;
@@ -173,98 +172,14 @@ public class DbObjectSchema {
public Method getComputedColumn(String name) {
return computedColumns.get(name);
}
public Object[] getInsertArray(Map<String, String> params, Map<String, MultipartFile> files) {
int currentIndex = 0;
String pkValue = params.get(getPrimaryKey().getName());
if (pkValue == null || pkValue.isBlank())
pkValue = null;
Object[] row;
if (pkValue == null) {
row = new Object[getSortedFields().size() - 1];
} else {
row = new Object[getSortedFields().size()];
}
for (DbField field : getSortedFields()) {
// Skip the primary key if the value is null
// If it is autogenerated, it will be filled by the database
// otherwise it will throw an error
if (field.isPrimaryKey() && pkValue == null) {
continue;
}
String name = field.getName();
String stringValue = params.get(name);
Object value = null;
if (stringValue != null && stringValue.isBlank()) stringValue = null;
if (stringValue != null) {
value = stringValue;
} else {
value = files.get(name);
}
String type = params.get("__dbadmin_" + name + "_type");
if (type == null)
throw new RuntimeException("Missing type hidden field for: " + name);
try {
if (value == null)
row[currentIndex++] = null;
else
row[currentIndex++] = DbFieldType.valueOf(type).parseValue(value);
} catch (IllegalArgumentException | SecurityException e) {
e.printStackTrace();
}
}
return row;
}
public Object[] getUpdateArray(Map<String, String> params, Map<String, MultipartFile> files) {
Object[] row = new Object[getSortedFields().size() + 1];
int currentIndex = 0;
DbField primaryKey = getPrimaryKey();
String pkValue = params.get(primaryKey.getName());
for (DbField field : getSortedFields()) {
String name = field.getName();
String stringValue = params.get(name);
Object value = null;
if (stringValue != null && stringValue.isBlank()) stringValue = null;
if (stringValue != null) {
value = stringValue;
} else {
value = files.get(name);
}
String type = params.get("__dbadmin_" + name + "_type");
if (type == null)
throw new RuntimeException("Missing type hidden field for: " + name);
try {
if (value == null)
row[currentIndex++] = null;
else
row[currentIndex++] = DbFieldType.valueOf(type).parseValue(value);
} catch (IllegalArgumentException | SecurityException e) {
e.printStackTrace();
}
}
row[currentIndex] = primaryKey.getType().parseValue(pkValue);
return row;
public List<DbField> getFilterableFields() {
return getSortedFields().stream().filter(f -> {
return !f.isBinary() && !f.isPrimaryKey()
&& f.getPrimitiveField().getAnnotation(Filterable.class) != null;
}).toList();
}
@Override
public String toString() {
return "DbObjectSchema [fields=" + fields + ", className=" + entityClass.getName() + "]";

View File

@@ -0,0 +1,52 @@
package tech.ailef.dbadmin.dto;
public enum CompareOperator {
GT {
@Override
public String getDisplayName() {
return "Greater than";
}
},
LT {
@Override
public String getDisplayName() {
return "Less than";
}
},
EQ {
@Override
public String getDisplayName() {
return "Equals";
}
},
STRING_EQ {
@Override
public String getDisplayName() {
return "Equals";
}
},
BEFORE {
@Override
public String getDisplayName() {
return "Before";
}
},
AFTER {
@Override
public String getDisplayName() {
return "After";
}
},
CONTAINS {
@Override
public String getDisplayName() {
return "Contains";
}
};
public abstract String getDisplayName();
public String toString() {
return this.name().toLowerCase();
}
}

View File

@@ -0,0 +1,37 @@
//package tech.ailef.dbadmin.dto;
//
//import java.util.Set;
//
//public class ListModelRequest {
// private String className;
//
// private String query;
//
// private Integer page;
//
// private Integer pageSize;
//
// private String sortKey;
//
// private String sortOrder;
//
// private Set<QueryFilter> queryFilters;
//
// private PaginationInfo paginationInfo;
//
// public ListModelRequest(String className, String query, Integer page, Integer pageSize, String sortKey,
// String sortOrder, Set<QueryFilter> queryFilters, PaginationInfo paginationInfo) {
// super();
// this.className = className;
// this.query = query;
// this.page = page;
// this.pageSize = pageSize;
// this.sortKey = sortKey;
// this.sortOrder = sortOrder;
// this.queryFilters = queryFilters;
// this.paginationInfo = paginationInfo;
// }
//
//
//// @RequestParam MultiValueMap<String, String> otherParams,
//}

View File

@@ -1,9 +1,15 @@
package tech.ailef.dbadmin.dto;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.springframework.util.MultiValueMap;
import tech.ailef.dbadmin.misc.Utils;
/**
* Attached as output to requests that have a paginated response,
* holds information about the current pagination.
@@ -30,12 +36,25 @@ public class PaginationInfo {
private int pageSize;
private long maxElement;
private Set<QueryFilter> queryFilters;
private String query;
private String sortKey;
private String sortOrder;
public PaginationInfo(int currentPage, int maxPage, int pageSize, long maxElement) {
public PaginationInfo(int currentPage, int maxPage, int pageSize, long maxElement, String query,
String sortKey, String sortOrder, Set<QueryFilter> queryFilters) {
this.currentPage = currentPage;
this.maxPage = maxPage;
this.pageSize = pageSize;
this.query = query;
this.maxElement = maxElement;
this.queryFilters = queryFilters;
this.sortKey = sortKey;
this.sortOrder = sortOrder;
}
public int getCurrentPage() {
@@ -65,6 +84,36 @@ public class PaginationInfo {
public long getMaxElement() {
return maxElement;
}
public String getSortedPageLink(String sortKey, String sortOrder) {
MultiValueMap<String, String> params = Utils.computeParams(queryFilters);
if (query != null) {
params.put("query", new ArrayList<>());
params.get("query").add(query);
}
params.add("pageSize", "" + pageSize);
params.add("page", "" + currentPage);
params.add("sortKey", sortKey);
params.add("sortOrder", sortOrder);
return Utils.getQueryString(params);
}
public String getLink(int page) {
MultiValueMap<String, String> params = Utils.computeParams(queryFilters);
if (query != null) {
params.put("query", new ArrayList<>());
params.get("query").add(query);
}
params.add("pageSize", "" + pageSize);
params.add("page", "" + page);
return Utils.getQueryString(params);
}
public List<Integer> getBeforePages() {
return IntStream.range(Math.max(currentPage - PAGE_RANGE, 1), currentPage).boxed().collect(Collectors.toList());

View File

@@ -0,0 +1,57 @@
package tech.ailef.dbadmin.dto;
import java.util.Objects;
public class QueryFilter {
private String field;
private CompareOperator op;
private String value;
public QueryFilter(String field, CompareOperator op, String value) {
this.field = field;
this.op = op;
this.value = value;
}
public String getField() {
return field;
}
public CompareOperator getOp() {
return op;
}
public String getValue() {
return value;
}
@Override
public int hashCode() {
return Objects.hash(field, op, value);
}
@Override
public String toString() {
String displayValue = value;
if (value.length() > 10) {
displayValue = value.substring(0, 4) + "..." + value.substring(value.length() - 4);
}
return "'" + field + "' " + op.getDisplayName() + " '" + displayValue + "'";
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
QueryFilter other = (QueryFilter) obj;
return Objects.equals(field, other.field) && Objects.equals(op, other.op) && Objects.equals(value, other.value);
}
}

View File

@@ -1,5 +1,17 @@
package tech.ailef.dbadmin.misc;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import tech.ailef.dbadmin.dto.CompareOperator;
import tech.ailef.dbadmin.dto.QueryFilter;
import tech.ailef.dbadmin.exceptions.DbAdminException;
public interface Utils {
public static String camelToSnake(String v) {
if (Character.isUpperCase(v.charAt(0))) {
@@ -10,6 +22,66 @@ public interface Utils {
}
public static MultiValueMap<String, String> computeParams(Set<QueryFilter> filters) {
MultiValueMap<String, String> r = new LinkedMultiValueMap<>();
r.put("filter_field", new ArrayList<>());
r.put("filter_op", new ArrayList<>());
r.put("filter_value", new ArrayList<>());
for (QueryFilter filter : filters) {
r.get("filter_field").add(filter.getField());
r.get("filter_op").add(filter.getOp().toString());
r.get("filter_value").add(filter.getValue());
}
return r;
}
public static Set<QueryFilter> computeFilters(MultiValueMap<String, String> params) {
if (params == null)
return new HashSet<>();
List<String> ops = params.get("filter_op");
List<String> fields = params.get("filter_field");
List<String> values = params.get("filter_value");
if (ops == null || fields == null || values == null)
return new HashSet<>();
if (ops.size() != fields.size() || fields.size() != values.size()
|| ops.size() != values.size()) {
throw new DbAdminException("Filtering parameters must have the same size");
}
Set<QueryFilter> filters = new HashSet<>();
for (int i = 0; i < ops.size(); i++) {
String op = ops.get(i);
String field = fields.get(i);
String value = values.get(i);
QueryFilter queryFilter = new QueryFilter(field, CompareOperator.valueOf(op.toUpperCase()), value);
filters.add(queryFilter);
}
return filters;
}
public static String getQueryString(MultiValueMap<String, String> params) {
Set<String> currentParams = params.keySet();
List<String> paramValues = new ArrayList<>();
for (String param : currentParams) {
for (String v : params.get(param)) {
paramValues.add(param + "=" + v.trim());
}
}
if (paramValues.isEmpty()) return "";
return "?" + String.join("&", paramValues);
}
public static String snakeToCamel(String text) {
boolean shouldConvertNextCharToLower = true;
StringBuilder builder = new StringBuilder();